diff --git a/.github/workflows/line-length-check.yml b/.github/workflows/line-length-check.yml
index b13eab4b..bef914a5 100644
--- a/.github/workflows/line-length-check.yml
+++ b/.github/workflows/line-length-check.yml
@@ -61,6 +61,14 @@ jobs:
continue
fi
+ # Skip certificate/key files. These carry encoded key material
+ # and OpenSSL text dumps (long Issuer/Subject lines) that cannot
+ # be wrapped
+ if [[ "$file" =~ \.(pem|der)$ ]]; then
+ echo "Skipping $file (certificate/key file)"
+ continue
+ fi
+
echo "Checking: $file"
# Get added lines with actual file line numbers and check their length
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index eec47005..70dbac84 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -176,6 +176,46 @@ jobs:
jdk_version: ${{ matrix.jdk_version }}
wolfssl_configure: ${{ matrix.wolfssl_configure }}
+ # ----------------------- SLH-DSA (FIPS 205) --------------------------
+ # SLH-DSA provides full keygen/sign/verify in wolfJCE and requires wolfSSL
+ # to be built with --enable-slhdsa. It is not pulled in by --enable-all.
+ # The --enable-slhdsa option is comma-separated:
+ # (default/yes) the six SHAKE parameter sets (128s/128f/192s/192f/
+ # 256s/256f), full keygen + sign + verify
+ # sha2 -> adds the six SHA2 parameter sets (SLH-DSA-SHA2-*)
+ # 128f, sha2-128f, ... -> select individual parameter sets
+ # small -> WOLFSSL_WC_SLHDSA_SMALL (low-memory codepath)
+ # small-mem -> WOLFSSL_WC_SLHDSA_SMALL_MEM (lower-memory codepath)
+ # verify-only -> WOLFSSL_SLHDSA_VERIFY_ONLY (no keygen/sign)
+ # Extra options do not imply 'yes': parameter sets must be selected via
+ # 'yes', 'sha2', or per-set tokens, otherwise no set is compiled in and
+ # native wolfSSL fails to build ("No parameters defined"). SHAKE256/SHA-3
+ # dependencies are auto-enabled by wolfSSL configure, so no --enable-sha3
+ # is needed. JUnit tests probe per parameter set and per capability, so
+ # each permutation runs what it can and skips the rest. Permutations:
+ # default SHAKE-only, all 12 sets, all 12 + small codepath, a reduced
+ # per-set selection + small-mem codepath, verify-only degradation, and
+ # --enable-all interop.
+ linux-zulu-slhdsa:
+ strategy:
+ matrix:
+ os: [ 'ubuntu-latest', 'macos-latest' ]
+ jdk_version: [ '11', '21', '24' ]
+ wolfssl_configure:
+ - '--enable-jni --enable-slhdsa'
+ - '--enable-jni --enable-slhdsa=yes,sha2'
+ - '--enable-jni --enable-slhdsa=yes,sha2,small'
+ - '--enable-jni --enable-slhdsa=128f,sha2-128f,small-mem'
+ - '--enable-jni --enable-slhdsa=yes,sha2,verify-only'
+ - '--enable-jni --enable-all --enable-slhdsa=yes,sha2'
+ name: ${{ matrix.os }} (Zulu JDK ${{ matrix.jdk_version }}, ${{ matrix.wolfssl_configure }})
+ uses: ./.github/workflows/linux-common.yml
+ with:
+ os: ${{ matrix.os }}
+ jdk_distro: "zulu"
+ jdk_version: ${{ matrix.jdk_version }}
+ wolfssl_configure: ${{ matrix.wolfssl_configure }}
+
# ------------------ Facebook Infer static analysis -------------------
# Run Facebook infer over PR code, only running on Linux with one
# JDK/version for now.
diff --git a/IDE/Android/app/src/main/cpp/CMakeLists.txt b/IDE/Android/app/src/main/cpp/CMakeLists.txt
index 6223b1b9..ab10d516 100644
--- a/IDE/Android/app/src/main/cpp/CMakeLists.txt
+++ b/IDE/Android/app/src/main/cpp/CMakeLists.txt
@@ -378,6 +378,7 @@ add_library(wolfcryptjni SHARED
${wolfcryptjni_DIR}/jni/jni_md5.c
${wolfcryptjni_DIR}/jni/jni_mldsa.c
${wolfcryptjni_DIR}/jni/jni_mlkem.c
+ ${wolfcryptjni_DIR}/jni/jni_slhdsa.c
${wolfcryptjni_DIR}/jni/jni_native_struct.c
${wolfcryptjni_DIR}/jni/jni_pwdbased.c
${wolfcryptjni_DIR}/jni/jni_rng.c
diff --git a/IDE/WIN/wolfcryptjni.vcxproj b/IDE/WIN/wolfcryptjni.vcxproj
index 666ff0cf..546008fb 100644
--- a/IDE/WIN/wolfcryptjni.vcxproj
+++ b/IDE/WIN/wolfcryptjni.vcxproj
@@ -101,6 +101,7 @@
+
diff --git a/IDE/WIN/wolfcryptjni.vcxproj.filters b/IDE/WIN/wolfcryptjni.vcxproj.filters
index 87f5c7b9..313faf34 100644
--- a/IDE/WIN/wolfcryptjni.vcxproj.filters
+++ b/IDE/WIN/wolfcryptjni.vcxproj.filters
@@ -197,6 +197,9 @@
Source Files
+
+ Source Files
+
Source Files
diff --git a/README.md b/README.md
index 9a71623d..069c414b 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,16 @@ JDK SUN provider: it registers the LMS `Signature` (verification) and
`KeyFactory` (public-key) services. wolfJCE does not generate LMS keys or sign,
since stateful hash-based signing belongs in hardware (NIST SP 800-208).
+**Note on SLH-DSA (FIPS 205):** SLH-DSA support is **not** enabled by default
+in `--enable-jni`. To use the SLH-DSA `Signature`, `KeyPairGenerator`, and
+`KeyFactory` services (and SLH-DSA keys in the WKS KeyStore), add
+`--enable-slhdsa` to the native wolfSSL `./configure` line. The parameter-set
+families are selected by the configure value: `--enable-slhdsa` builds the six
+SHAKE sets, and `--enable-slhdsa=yes,sha2` builds all twelve and is the
+recommended configuration for SHA2 parameter-set support (the
+`SLH-DSA-SHA2-128f` default lives in the SHA2 family). Without SLH-DSA, wolfJCE
+compiles and runs normally but the SLH-DSA services are not registered.
+
**wolfSSL Standard Build**:
```
$ cd wolfssl-x.x.x
diff --git a/README_JCE.md b/README_JCE.md
index 3abde61e..0450e6ba 100644
--- a/README_JCE.md
+++ b/README_JCE.md
@@ -235,6 +235,56 @@ The JCE provider currently supports the following algorithms:
OID: 1.3.6.1.5.5.7.6.35
LMS (also registered as HSS/LMS)
OID: 1.2.840.113549.1.9.16.3.17
+ SLH-DSA (any SLH-DSA parameter set key)
+ SLH-DSA-SHA2-128s
+ OID: 2.16.840.1.101.3.4.3.20
+ SLH-DSA-SHA2-128f
+ OID: 2.16.840.1.101.3.4.3.21
+ SLH-DSA-SHA2-192s
+ OID: 2.16.840.1.101.3.4.3.22
+ SLH-DSA-SHA2-192f
+ OID: 2.16.840.1.101.3.4.3.23
+ SLH-DSA-SHA2-256s
+ OID: 2.16.840.1.101.3.4.3.24
+ SLH-DSA-SHA2-256f
+ OID: 2.16.840.1.101.3.4.3.25
+ SLH-DSA-SHAKE-128s
+ OID: 2.16.840.1.101.3.4.3.26
+ SLH-DSA-SHAKE-128f
+ OID: 2.16.840.1.101.3.4.3.27
+ SLH-DSA-SHAKE-192s
+ OID: 2.16.840.1.101.3.4.3.28
+ SLH-DSA-SHAKE-192f
+ OID: 2.16.840.1.101.3.4.3.29
+ SLH-DSA-SHAKE-256s
+ OID: 2.16.840.1.101.3.4.3.30
+ SLH-DSA-SHAKE-256f
+ OID: 2.16.840.1.101.3.4.3.31
+ HASH-SLH-DSA (pre-hash, any SLH-DSA parameter set key)
+ SLH-DSA-SHA2-128s-WITH-SHA256
+ OID: 2.16.840.1.101.3.4.3.35
+ SLH-DSA-SHA2-128f-WITH-SHA256
+ OID: 2.16.840.1.101.3.4.3.36
+ SLH-DSA-SHA2-192s-WITH-SHA512
+ OID: 2.16.840.1.101.3.4.3.37
+ SLH-DSA-SHA2-192f-WITH-SHA512
+ OID: 2.16.840.1.101.3.4.3.38
+ SLH-DSA-SHA2-256s-WITH-SHA512
+ OID: 2.16.840.1.101.3.4.3.39
+ SLH-DSA-SHA2-256f-WITH-SHA512
+ OID: 2.16.840.1.101.3.4.3.40
+ SLH-DSA-SHAKE-128s-WITH-SHAKE128
+ OID: 2.16.840.1.101.3.4.3.41
+ SLH-DSA-SHAKE-128f-WITH-SHAKE128
+ OID: 2.16.840.1.101.3.4.3.42
+ SLH-DSA-SHAKE-192s-WITH-SHAKE256
+ OID: 2.16.840.1.101.3.4.3.43
+ SLH-DSA-SHAKE-192f-WITH-SHAKE256
+ OID: 2.16.840.1.101.3.4.3.44
+ SLH-DSA-SHAKE-256s-WITH-SHAKE256
+ OID: 2.16.840.1.101.3.4.3.45
+ SLH-DSA-SHAKE-256f-WITH-SHAKE256
+ OID: 2.16.840.1.101.3.4.3.46
KeyAgreement Class
DiffieHellman
@@ -262,6 +312,19 @@ The JCE provider currently supports the following algorithms:
ML-DSA-44 (alias OID: 2.16.840.1.101.3.4.3.17)
ML-DSA-65 (alias OID: 2.16.840.1.101.3.4.3.18)
ML-DSA-87 (alias OID: 2.16.840.1.101.3.4.3.19)
+ SLH-DSA (defaults to SLH-DSA-SHA2-128f, set overridable via init())
+ SLH-DSA-SHA2-128s (alias OID: 2.16.840.1.101.3.4.3.20)
+ SLH-DSA-SHA2-128f (alias OID: 2.16.840.1.101.3.4.3.21)
+ SLH-DSA-SHA2-192s (alias OID: 2.16.840.1.101.3.4.3.22)
+ SLH-DSA-SHA2-192f (alias OID: 2.16.840.1.101.3.4.3.23)
+ SLH-DSA-SHA2-256s (alias OID: 2.16.840.1.101.3.4.3.24)
+ SLH-DSA-SHA2-256f (alias OID: 2.16.840.1.101.3.4.3.25)
+ SLH-DSA-SHAKE-128s (alias OID: 2.16.840.1.101.3.4.3.26)
+ SLH-DSA-SHAKE-128f (alias OID: 2.16.840.1.101.3.4.3.27)
+ SLH-DSA-SHAKE-192s (alias OID: 2.16.840.1.101.3.4.3.28)
+ SLH-DSA-SHAKE-192f (alias OID: 2.16.840.1.101.3.4.3.29)
+ SLH-DSA-SHAKE-256s (alias OID: 2.16.840.1.101.3.4.3.30)
+ SLH-DSA-SHAKE-256f (alias OID: 2.16.840.1.101.3.4.3.31)
ML-KEM (defaults to ML-KEM-768, level overridable via init())
ML-KEM-512 (alias OID: 2.16.840.1.101.3.4.4.1)
ML-KEM-768 (alias OID: 2.16.840.1.101.3.4.4.2)
@@ -275,6 +338,19 @@ The JCE provider currently supports the following algorithms:
ML-DSA-44 (alias OID: 2.16.840.1.101.3.4.3.17)
ML-DSA-65 (alias OID: 2.16.840.1.101.3.4.3.18)
ML-DSA-87 (alias OID: 2.16.840.1.101.3.4.3.19)
+ SLH-DSA
+ SLH-DSA-SHA2-128s (alias OID: 2.16.840.1.101.3.4.3.20)
+ SLH-DSA-SHA2-128f (alias OID: 2.16.840.1.101.3.4.3.21)
+ SLH-DSA-SHA2-192s (alias OID: 2.16.840.1.101.3.4.3.22)
+ SLH-DSA-SHA2-192f (alias OID: 2.16.840.1.101.3.4.3.23)
+ SLH-DSA-SHA2-256s (alias OID: 2.16.840.1.101.3.4.3.24)
+ SLH-DSA-SHA2-256f (alias OID: 2.16.840.1.101.3.4.3.25)
+ SLH-DSA-SHAKE-128s (alias OID: 2.16.840.1.101.3.4.3.26)
+ SLH-DSA-SHAKE-128f (alias OID: 2.16.840.1.101.3.4.3.27)
+ SLH-DSA-SHAKE-192s (alias OID: 2.16.840.1.101.3.4.3.28)
+ SLH-DSA-SHAKE-192f (alias OID: 2.16.840.1.101.3.4.3.29)
+ SLH-DSA-SHAKE-256s (alias OID: 2.16.840.1.101.3.4.3.30)
+ SLH-DSA-SHAKE-256f (alias OID: 2.16.840.1.101.3.4.3.31)
ML-KEM
ML-KEM-512 (alias OID: 2.16.840.1.101.3.4.4.1)
ML-KEM-768 (alias OID: 2.16.840.1.101.3.4.4.2)
@@ -422,9 +498,10 @@ FIPS 140-2/3.
#### Stored Object Compatibility
The WKS KeyStore supports storage of PrivateKey, Certificate, and SecretKey
-objects. PrivateKey storage includes RSA, ECC, and ML-DSA (FIPS 204) keys.
-ML-DSA key support requires native wolfSSL to be built with ML-DSA enabled
-(see the ML-DSA note in [README.md](./README.md)).
+objects. PrivateKey storage includes RSA, ECC, ML-DSA (FIPS 204), and SLH-DSA
+(FIPS 205) keys. ML-DSA and SLH-DSA key support requires native wolfSSL to be
+built with the respective algorithm enabled (see the ML-DSA and SLH-DSA notes
+in [README.md](./README.md)).
#### Converting Other KeyStore Formats to WKS
diff --git a/examples/certs/slhdsa/root-slhdsa-sha2-128s-priv.pem b/examples/certs/slhdsa/root-slhdsa-sha2-128s-priv.pem
new file mode 100644
index 00000000..4607e165
--- /dev/null
+++ b/examples/certs/slhdsa/root-slhdsa-sha2-128s-priv.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MFICAQAwCwYJYIZIAWUDBAMUBEBIDLnGQ7/b2dqaWSNZ6GNUDPTUVnm6hv7L66kx
+FwCox6bgJKhKGBBNzcbaIyRn3paVgQCmys5D5P38exqM+ut+
+-----END PRIVATE KEY-----
diff --git a/examples/certs/slhdsa/root-slhdsa-sha2-128s.pem b/examples/certs/slhdsa/root-slhdsa-sha2-128s.pem
new file mode 100644
index 00000000..0c646aa9
--- /dev/null
+++ b/examples/certs/slhdsa/root-slhdsa-sha2-128s.pem
@@ -0,0 +1,644 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 65:47:75:b4:58:fa:b2:e1:1f:16:b5:5f:25:49:b4:7f:2a:12:60:56
+ Signature Algorithm: SLH-DSA-SHA2-128s
+ Issuer: C=US, ST=Montana, L=Bozeman, O=wolfSSL_SLH-DSA, OU=Root-SLH-DSA-sha2, CN=www.wolfssl.com, emailAddress=info@wolfssl.com
+ Validity
+ Not Before: Apr 28 08:10:05 2026 GMT
+ Not After : Jan 22 08:10:05 2029 GMT
+ Subject: C=US, ST=Montana, L=Bozeman, O=wolfSSL_SLH-DSA, OU=Root-SLH-DSA-sha2, CN=www.wolfssl.com, emailAddress=info@wolfssl.com
+ Subject Public Key Info:
+ Public Key Algorithm: SLH-DSA-SHA2-128s
+ SLH-DSA-SHA2-128s Public-Key:
+ pub:
+ a6:e0:24:a8:4a:18:10:4d:cd:c6:da:23:24:67:de:
+ 96:95:81:00:a6:ca:ce:43:e4:fd:fc:7b:1a:8c:fa:
+ eb:7e
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ BD:80:23:3A:06:DD:38:57:EE:6B:C2:94:7B:EA:BF:43:57:3A:B0:8C
+ X509v3 Authority Key Identifier:
+ BD:80:23:3A:06:DD:38:57:EE:6B:C2:94:7B:EA:BF:43:57:3A:B0:8C
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ Signature Algorithm: SLH-DSA-SHA2-128s
+ Signature Value:
+ 10:3c:70:c5:f5:b1:4f:1c:77:85:34:b9:b3:57:97:62:ef:e7:
+ 4d:17:67:56:63:74:02:e1:50:5c:5b:dc:56:94:45:d7:f9:6b:
+ 76:fd:fb:e2:00:16:0b:5a:0f:86:86:7e:1e:0c:4e:7f:d0:95:
+ cb:d4:8d:09:58:53:4e:e2:9f:0e:11:2e:38:dc:c0:87:ec:6f:
+ 25:ef:1b:59:81:ab:71:69:51:2e:3c:2f:f6:eb:f2:22:73:62:
+ e1:a2:68:b3:67:db:2e:05:16:fa:bc:ae:b6:22:cb:c2:0f:fd:
+ 88:1e:36:43:27:13:de:61:01:0c:ba:5a:df:0a:69:17:45:c2:
+ 77:b5:df:87:62:b3:16:6e:8e:57:e0:3b:9f:02:80:5d:f0:92:
+ 76:51:73:2e:7a:25:1c:88:79:dd:0d:55:c5:73:94:b3:76:52:
+ 39:fb:58:0d:34:fe:74:38:45:fc:99:39:87:c7:fe:4b:2a:7c:
+ 51:ae:92:ee:5f:28:16:13:04:b5:f0:5f:93:90:74:d0:e0:f4:
+ 1d:06:af:6b:ad:33:fe:2c:d5:9a:e5:10:32:7f:01:2f:7e:97:
+ c6:ff:b6:57:97:56:cc:5a:5a:9a:79:de:19:b0:9c:0b:57:bb:
+ bb:e5:8b:91:2c:cd:19:2d:7c:75:8e:71:4f:c5:c5:88:74:5c:
+ 5d:27:82:dc:94:58:7d:6e:71:6e:78:c5:f3:0d:3b:85:95:2a:
+ da:4d:af:34:a9:3c:02:88:cc:45:1d:08:0c:9d:20:39:73:06:
+ 0a:2d:ba:8a:5d:a9:44:32:24:32:b1:d1:fd:d1:7f:b4:10:56:
+ 3a:15:12:a5:f6:c1:6f:16:ef:84:5e:86:ab:5b:9b:a7:b4:21:
+ e3:43:86:1a:50:f8:95:b3:b0:10:56:0e:26:b6:5a:46:75:51:
+ e2:7a:5a:76:2b:14:4e:57:5d:88:61:27:16:8c:e0:6a:ad:87:
+ b1:19:89:80:87:aa:6a:59:69:18:fc:e3:5a:78:83:d8:58:3a:
+ ec:8e:b9:28:2a:22:e6:13:8f:ad:df:4e:4d:9e:99:6b:34:9a:
+ c5:c1:81:0e:28:1a:16:e5:60:d1:7d:1c:d7:5f:f8:3e:7d:1c:
+ 7c:2d:18:5f:3a:5c:d2:d2:0b:db:c4:4f:69:57:a7:6d:60:95:
+ 5e:04:fb:71:31:71:d1:e4:ee:dd:10:34:a5:9b:0c:7b:29:e9:
+ 4b:85:3a:2f:64:87:93:9a:8f:06:9f:75:8a:c2:b3:a6:0c:ff:
+ 94:7a:c8:f4:e6:14:31:b1:6f:20:ad:34:5c:3f:1a:67:c7:0e:
+ 0e:89:00:c5:9f:d6:56:4b:8d:86:dc:64:94:e0:7e:4d:8b:cd:
+ 43:5f:3b:46:c9:92:f0:a4:0d:df:b9:ab:38:aa:5b:3b:9c:af:
+ fa:fb:d5:f6:0b:25:96:30:33:28:3d:3f:1e:a6:10:43:58:53:
+ ae:88:a7:5b:8e:55:66:57:2e:0d:88:a9:5b:d3:71:0a:23:05:
+ 47:82:ca:e6:0c:c8:d1:f2:99:9f:c9:37:2a:59:99:d4:f0:a5:
+ 82:18:0b:98:f0:ee:be:0f:11:6f:2a:6a:14:dd:c4:c9:bc:ed:
+ 80:ac:7c:ca:65:eb:fa:af:b3:29:a7:b2:c3:1c:42:1e:7d:3f:
+ d3:1a:f0:1b:30:f5:a3:53:8c:13:d3:96:82:01:a8:2e:d2:ae:
+ ea:f1:50:17:ae:cb:a4:7b:55:72:55:8b:90:28:f0:d9:5b:81:
+ f4:78:9b:b0:fe:a0:c4:6e:dc:25:fc:94:64:71:12:bf:fc:cd:
+ 31:50:26:2d:e2:ef:38:05:14:82:b6:62:83:a9:86:cb:df:05:
+ ef:27:6a:cc:2c:8f:1c:5d:40:13:c1:e3:24:a6:7d:3c:83:1a:
+ 78:79:7d:63:75:4f:68:b9:9c:75:b9:07:ed:62:16:29:22:c1:
+ cd:f2:52:02:e9:17:b0:d0:44:13:9c:25:c7:fd:39:df:eb:d5:
+ 32:5e:bf:c5:47:81:18:83:63:96:89:4c:31:02:8f:c4:e9:7f:
+ 27:8e:01:3f:44:4e:96:b0:73:ad:56:a7:95:4d:ab:a3:03:e2:
+ dc:33:65:d2:17:87:e8:07:be:95:17:64:3e:6c:87:cd:3a:e6:
+ 0a:b8:e6:8b:e2:e9:d9:60:2a:ef:d5:c8:2f:7a:8d:ec:70:16:
+ 10:a2:1d:12:16:41:a3:c1:07:2f:95:b1:d4:7f:9f:ca:16:ca:
+ 72:1d:2d:df:3a:6f:93:eb:9e:76:34:3f:dd:a7:47:12:18:5b:
+ f4:34:2b:73:42:33:15:c8:19:0c:a8:10:d5:d3:04:81:e2:d1:
+ b4:3f:7e:53:34:d8:db:02:d6:60:b4:24:8c:28:4d:31:51:0b:
+ 4e:c8:f1:4c:99:98:a7:36:ab:bc:ce:a2:d6:67:f5:b2:64:ec:
+ 0e:e3:f3:82:46:d3:87:ce:1b:a9:46:d3:dc:1a:27:e2:5f:bf:
+ 55:33:ca:e0:de:02:84:26:ed:57:58:27:f4:d6:3c:0d:a7:db:
+ 40:2d:d8:c8:0c:84:47:97:fb:bf:49:8c:d2:1c:80:ff:da:d6:
+ 3f:93:00:77:f3:31:f0:c8:57:46:5a:9a:15:25:0b:2d:d1:1b:
+ 5a:eb:ae:09:0e:3e:24:e6:a9:eb:72:be:e4:45:d2:41:db:21:
+ dd:a7:1b:6e:62:3e:43:d9:62:34:31:62:76:d4:2e:39:48:fd:
+ 54:9c:51:6a:f7:71:44:88:c0:c3:98:3b:66:e1:8e:0a:9d:e3:
+ 68:a9:6a:d2:52:80:97:3d:9e:14:c9:bc:36:33:6f:24:9c:7b:
+ e1:8a:a5:a1:12:44:bf:61:a2:3b:da:18:b9:7f:9f:89:bb:10:
+ 33:8f:46:18:92:49:5e:10:81:a6:0b:06:19:19:a3:e8:06:c8:
+ fe:4e:94:20:de:70:36:7b:40:ee:04:ca:fc:fa:81:aa:87:9d:
+ ad:f3:57:78:0c:34:51:f4:fa:ef:78:58:1e:e9:0f:b9:75:f6:
+ a5:09:a0:91:7f:80:19:80:17:4b:5c:a9:b9:6d:b6:00:f8:17:
+ 90:e1:b7:4f:21:33:64:1d:d1:d5:a8:78:a0:98:46:0a:0a:0e:
+ 72:dc:2a:67:8e:57:44:00:76:2e:79:b9:4e:c7:78:b2:a7:12:
+ 17:75:44:6c:ce:a6:d7:ae:25:54:6c:5d:eb:3d:92:74:65:26:
+ f7:6d:23:1a:43:91:ad:2e:20:c8:f4:fa:13:a9:97:ea:7e:a9:
+ 61:94:81:a7:cd:b3:60:a9:62:4c:ec:8b:91:21:a9:d3:7c:39:
+ 9f:3f:4c:f8:8f:16:81:3f:23:3a:20:64:7a:10:ce:82:d7:24:
+ 28:ea:e0:74:6a:ea:62:bd:e3:06:90:1e:92:ca:fa:2d:25:a5:
+ 70:52:8f:a1:3f:9f:7a:7a:f3:55:4d:4b:8b:1d:1e:ce:d0:61:
+ 44:68:e5:c2:69:e8:14:2f:5c:90:96:72:8f:7e:39:61:c1:d4:
+ 3d:36:bf:12:b4:de:31:3e:7e:f4:45:d9:6b:d8:9c:61:06:fe:
+ 7d:43:ae:e3:c8:f4:3b:0d:7a:5d:b6:35:46:5d:5b:30:e9:9f:
+ de:49:53:d8:a7:97:61:33:ec:fe:dd:4a:89:c3:db:aa:39:4b:
+ 01:f8:20:27:a3:0b:12:2f:03:fd:8d:69:5f:90:2a:62:97:93:
+ a6:d4:12:80:ee:98:47:93:d9:89:7d:46:f3:2d:61:8e:e6:9d:
+ 7e:7e:42:07:0f:3e:15:08:9b:49:36:98:1c:1e:dc:57:5d:eb:
+ ab:35:4b:33:eb:2f:fd:a5:6e:92:45:5d:21:df:d4:74:83:02:
+ a9:3c:bd:88:ed:d7:05:da:b5:2a:56:e1:ae:7c:53:a8:9d:f3:
+ 24:bf:82:1d:09:20:66:b9:b8:4b:ee:b7:e0:9a:15:e3:99:bc:
+ e3:93:a4:2e:93:4a:c2:0c:8b:42:ee:29:42:f5:93:e9:b6:73:
+ 8d:8d:24:4c:78:26:b7:dc:64:c7:94:8e:f3:2f:81:68:91:a5:
+ 57:ec:c7:b3:7d:73:02:d0:20:18:16:7a:3b:dd:e9:65:08:84:
+ 6b:40:19:f6:79:71:13:ca:8f:be:8a:ae:c2:a4:67:ae:0e:5b:
+ a8:55:01:38:5a:0e:0a:51:04:57:12:76:e6:cf:d8:57:25:a7:
+ 8c:03:da:22:13:dd:90:e1:27:26:e0:d8:dc:9b:5a:6b:3a:0f:
+ e2:e3:8d:54:73:6e:12:02:72:f4:2b:c3:9b:80:3f:6f:fb:8a:
+ 9e:fd:bb:e4:d7:c6:14:8a:19:d6:9f:30:bc:00:98:41:92:2b:
+ bd:a5:8e:ab:93:89:9d:6e:d4:8e:82:17:88:a6:19:2b:8b:a4:
+ 67:e9:5b:ee:7f:bb:e6:a4:d7:cd:85:7a:c1:2d:15:8b:3d:4e:
+ 25:71:63:5c:7e:72:b7:bc:e7:8e:a8:ad:d5:a4:2f:d2:56:cf:
+ b1:29:49:03:9e:d3:b9:ce:35:60:8d:d3:50:97:5f:4e:ac:72:
+ f4:b0:20:e4:91:75:cc:27:5e:ce:ae:04:77:1b:84:02:fe:0c:
+ bc:a7:dd:62:3e:55:e9:e7:5b:a3:c8:1d:08:34:18:53:bd:fa:
+ 05:0c:e9:fc:84:73:0d:4b:9f:56:52:e5:09:e8:46:43:3b:4e:
+ 59:fa:84:67:d5:c2:94:3d:ea:cb:9d:29:31:4d:91:61:90:f2:
+ 1f:29:68:dc:34:5f:13:fd:b1:f2:38:72:85:f1:16:1c:7f:05:
+ e5:b8:32:57:3f:6a:68:ce:e5:58:1a:14:e5:1f:e7:b7:1c:04:
+ a6:e1:c5:86:c8:78:e3:62:92:fd:ff:9b:f1:e8:60:9c:40:b9:
+ 1c:1d:7b:52:ad:ee:de:fa:04:bf:0b:bd:d9:34:bf:ef:4a:1b:
+ f2:0c:72:43:1e:55:24:b1:56:ae:0c:44:99:ef:b5:fb:e5:ee:
+ c6:20:7c:15:5f:66:c8:32:c3:e0:33:76:00:d3:b0:7e:be:0a:
+ 28:79:3f:98:51:78:12:73:ae:b3:24:a5:a1:d3:1c:f8:6c:5a:
+ e7:c0:47:5f:86:a5:67:d5:3b:20:19:49:91:fd:c6:32:0c:96:
+ e9:df:ea:e1:8e:cf:4a:bd:9f:95:eb:88:2c:3c:f6:34:da:89:
+ f7:3a:d1:bb:0f:97:f0:38:99:ec:89:b4:b3:92:bf:ed:13:7d:
+ bc:14:54:16:63:01:f3:60:f0:f6:c4:e0:db:00:dc:c8:5f:13:
+ 11:82:1d:04:73:a4:f1:8d:d0:8e:7a:4d:1f:9c:0e:8d:dc:fe:
+ 7d:8f:97:5e:a1:97:c2:73:e7:45:94:19:19:93:f5:4f:fc:25:
+ 8b:c3:1a:19:a7:1d:91:0a:bf:28:78:41:06:7d:09:d4:95:c5:
+ d2:8f:47:04:cf:3f:9a:be:72:b0:8a:d0:29:0d:87:b8:22:11:
+ 2a:c6:c7:27:81:12:6f:9e:18:88:9c:8a:25:04:05:66:b1:ef:
+ b1:4c:48:f7:f7:53:ec:c8:72:bc:70:25:5e:19:be:3c:d7:f6:
+ 1a:52:77:05:15:db:ba:99:2a:d6:bd:ce:a1:70:fc:30:40:cc:
+ bd:4d:e4:36:f0:fc:b9:54:31:48:d3:9c:bb:3b:51:08:a5:e8:
+ b0:3b:5b:0a:69:91:2c:f7:d4:50:07:4c:a8:2f:7b:43:82:64:
+ fb:c3:ca:48:5c:b9:f7:f3:bc:d6:21:43:c3:b5:26:61:06:86:
+ 23:c6:7f:05:d7:0b:c1:e5:7d:5f:5a:c3:09:1a:7c:06:cb:35:
+ f8:a3:70:13:14:37:b6:98:00:88:24:b0:90:4f:f4:99:99:75:
+ d5:15:23:7b:5a:09:db:33:30:8b:b9:da:b2:08:7c:ea:39:3c:
+ 88:96:a8:39:da:1e:22:5a:b3:be:f7:40:58:7a:6d:7b:88:de:
+ 4c:57:30:f1:38:a5:cc:6a:e7:b3:c4:ef:8a:f1:4d:ca:2c:bf:
+ 5b:05:02:30:97:10:12:dd:3f:8a:bf:0a:86:4f:57:63:79:bc:
+ 2c:21:a2:c2:07:26:a4:96:d2:1a:d2:73:86:a9:af:2d:ca:ae:
+ 6f:07:e7:26:f4:47:65:8e:0e:29:8f:ca:ce:16:cd:90:00:f0:
+ a6:19:a4:30:b8:d7:8a:da:de:5a:5b:54:91:9a:e1:75:6b:49:
+ f6:b8:67:0d:02:d2:c4:ec:67:f5:0f:c2:dc:11:c5:65:69:f5:
+ 1f:44:44:af:1f:91:9c:29:1f:8b:e1:8e:9d:5c:9e:ac:72:fa:
+ 4b:c9:29:9a:f1:1e:d1:64:6b:8e:22:5a:34:0f:53:47:ce:0b:
+ e3:07:49:cb:86:de:35:ec:17:e4:d5:df:cb:f4:de:0c:e1:b8:
+ 93:97:88:9e:f6:0d:b3:fa:f7:4b:a6:52:ef:48:61:c1:43:f2:
+ 32:f7:af:77:ab:75:c0:6c:08:b6:cd:63:cb:b6:64:e8:01:ed:
+ e4:62:39:36:38:ab:70:4d:c3:a4:b4:92:be:79:e0:f2:7d:7f:
+ 06:52:48:bd:ba:c2:56:6d:af:1f:86:b6:7a:eb:e9:b4:fc:8c:
+ 13:83:fc:25:f8:93:5f:52:c4:bb:4a:08:5a:a0:bf:bd:24:37:
+ 4b:44:e4:a7:ea:fa:36:49:dd:86:1a:f9:f0:24:1b:ab:c1:1e:
+ 8c:03:f8:8a:86:99:fd:3d:9d:9f:68:1a:96:47:73:18:22:3b:
+ 7b:c0:76:02:1d:61:79:73:ac:f9:2c:45:ce:87:53:b5:f5:44:
+ 8d:73:9b:38:d9:bb:a4:11:fd:f8:0c:16:c6:74:49:f0:e0:18:
+ 10:c6:d1:2c:2b:5d:c4:17:72:60:e0:7c:d9:48:b3:eb:38:ba:
+ 4f:e9:82:c2:10:9a:cd:bd:8e:e8:ff:be:f0:57:d8:e5:94:50:
+ 15:6d:87:42:32:8b:d7:8a:07:22:8e:e2:31:e7:95:9b:22:4f:
+ 5d:0d:51:02:49:1e:fc:66:51:bb:81:01:96:64:63:d8:57:43:
+ 66:0d:1f:6c:b4:9a:16:b6:dd:09:c8:6d:0e:c0:79:1f:a6:49:
+ c8:11:79:d1:18:34:9a:4e:4f:1d:80:1e:47:5b:75:07:a1:74:
+ fa:52:d2:69:d1:24:d7:2a:cd:72:c6:c8:ec:7d:6b:6b:b6:f9:
+ e5:5e:9e:d4:24:a0:00:df:ab:b9:6d:57:58:90:18:ac:1c:bd:
+ af:c1:88:d8:90:5c:82:de:fa:4b:40:51:01:6d:45:c2:83:18:
+ a3:38:ca:e6:5d:c0:5a:dd:b9:68:0e:0d:c3:c2:f8:6a:8c:ee:
+ a8:6f:19:c2:d6:78:d6:57:33:7a:fb:c6:0a:db:47:aa:55:89:
+ 47:a6:20:6f:0b:78:84:69:20:da:b5:6c:1b:d3:2c:d7:db:94:
+ 38:e0:75:e7:6c:8f:4d:f5:e5:42:16:c6:91:93:6a:41:39:a5:
+ 77:92:02:49:63:0f:74:c3:b2:55:3f:b1:33:e5:7b:d9:ff:59:
+ ae:a1:6e:1c:06:bf:76:98:dc:ae:94:39:49:3f:5d:d6:b0:fa:
+ 06:3e:29:8e:40:62:b8:b7:dc:22:11:50:48:19:41:37:c5:e4:
+ 34:b8:25:60:98:ed:b9:4a:68:47:a9:e6:2d:d7:e5:ef:35:e7:
+ 6f:a8:9f:a5:b2:0f:0d:98:fa:9e:ff:fb:c5:57:83:69:98:b0:
+ 37:06:fb:4c:0d:17:ad:e9:68:2b:c3:42:1c:f6:0c:ac:7f:ca:
+ 1b:ba:a9:90:b8:f6:e1:a2:2a:45:8d:f5:aa:e5:8b:4a:16:f6:
+ 0e:3a:38:ee:25:d9:5f:b6:f8:fc:7b:a8:c6:88:e3:ea:f0:1f:
+ a1:b0:e7:46:e4:b0:59:08:95:02:25:0c:6a:8a:3d:56:3d:c9:
+ a8:12:dd:47:b4:2d:5a:f6:ae:43:cc:3e:4c:7f:4e:ab:b1:78:
+ 2a:59:6d:6f:ca:93:21:e4:f6:ad:91:a6:1d:da:a9:16:8f:e6:
+ 1a:d2:c9:22:13:62:8f:be:28:96:bd:23:59:38:91:75:b5:84:
+ bc:ae:b9:5f:07:4a:dd:fd:a8:fc:e6:6d:0d:04:e2:76:fe:30:
+ 0b:e9:9b:60:1c:98:bf:92:80:57:45:ba:53:d2:61:f7:50:51:
+ d2:ad:9c:03:39:96:39:ee:45:64:04:b3:ca:02:bf:ec:3b:d1:
+ ab:ec:8d:97:9a:ec:1a:ac:62:99:85:f6:15:c7:d9:b0:2b:f4:
+ dc:55:f4:19:ac:7e:36:59:70:ee:8c:79:98:89:61:86:25:bd:
+ b3:38:9e:49:ed:8a:ce:61:1d:d1:13:99:96:74:9e:4f:43:78:
+ c0:c9:a2:f3:62:92:f8:aa:6b:ca:91:7e:29:d1:80:27:64:68:
+ e1:0f:9b:fc:fe:64:99:0f:d6:8e:d3:78:d8:ea:fd:e5:0a:55:
+ 34:8b:f1:b4:d9:1b:a5:bb:30:f9:1a:f4:19:3c:3d:af:ac:5f:
+ ce:9b:27:08:18:52:6f:b0:aa:02:0d:46:84:73:92:f1:0b:16:
+ 12:9b:cf:bc:f3:97:bc:c2:9c:18:57:cc:55:63:21:de:f7:32:
+ c1:3e:fb:88:de:f7:ae:f0:11:10:5b:5f:04:df:9d:3d:19:2f:
+ 89:cb:72:b4:5b:f2:49:81:76:c2:57:ec:d9:44:ff:b8:8d:19:
+ 6a:2d:bf:31:47:34:e0:b1:03:32:26:15:e4:d7:44:41:d1:5e:
+ f9:ba:e2:5f:50:45:44:08:44:7f:9b:09:f4:37:ff:8b:d9:d6:
+ 55:66:d0:60:b2:8b:94:34:8d:97:dd:73:2f:02:22:be:9a:bd:
+ d3:57:8c:c3:c6:e1:e1:19:64:f1:6d:3e:41:20:6f:4e:47:bb:
+ 29:41:bd:a9:8c:ab:01:41:ea:2a:51:c7:f2:9f:ec:ca:9b:1e:
+ be:e7:f9:3a:5e:66:f8:dd:30:3c:dd:3d:53:61:1e:ad:93:31:
+ 01:c4:c4:b3:2c:b1:14:a3:68:a2:a2:6a:f9:60:e7:63:19:e4:
+ 89:19:89:1d:ad:de:85:5d:43:4c:57:61:17:b4:7d:f9:18:93:
+ 93:c6:88:40:0a:3d:7e:a4:e9:ab:fd:56:c0:2b:5f:35:68:26:
+ d4:e2:c8:f7:75:08:1c:11:4f:a0:64:a8:15:2d:f2:e9:58:0a:
+ 31:ec:cb:f8:ba:3b:85:aa:95:93:5e:47:05:27:c4:32:6d:56:
+ 71:6f:38:8d:db:93:99:57:35:cf:25:83:99:77:d3:7e:93:cd:
+ a0:04:ae:e5:e9:20:e7:63:b1:ed:69:8c:46:3d:1c:f6:84:bf:
+ 67:a5:71:60:d5:ec:9f:5b:d9:12:12:9e:0a:b2:85:40:94:34:
+ 9a:74:6b:25:1e:3b:03:2b:2c:55:7f:51:4a:6a:93:07:dd:e6:
+ d4:89:fb:1a:95:1d:cd:bb:d1:61:47:6d:8c:09:56:44:b3:52:
+ c9:ca:df:a1:27:45:77:a1:e3:31:ec:da:b4:ad:15:20:ac:f7:
+ cc:88:1a:b6:84:f5:21:c9:4f:f7:94:bb:d2:1c:d2:19:40:52:
+ 5d:2f:9f:44:d3:64:8c:88:d3:08:93:ff:5e:d1:99:63:60:40:
+ 73:08:bb:20:3d:d1:26:a8:30:48:44:2d:d3:65:e6:af:eb:22:
+ ec:7c:6f:97:16:69:58:3b:e2:ef:e9:de:de:90:10:07:f4:bd:
+ 6a:a7:e5:82:e9:22:d5:0e:c9:32:ea:ac:30:69:c6:96:9b:51:
+ 6b:93:65:67:e5:84:2f:36:1c:19:d8:be:26:fa:79:05:e0:ed:
+ 34:fd:2c:a4:c0:44:7f:cd:c7:51:e5:a9:81:a7:90:97:46:1a:
+ 78:00:16:64:2c:d9:d4:85:b6:61:1a:87:de:96:f5:0c:f3:cc:
+ 73:d4:0d:5e:8f:64:b0:a0:97:c9:12:0c:2e:fa:3a:a1:07:1c:
+ bc:29:0c:e0:82:b7:3b:0d:6b:14:80:3a:11:a6:d4:f4:36:a4:
+ 96:d9:89:f8:81:61:77:79:02:6f:5e:64:2a:d7:f2:fa:26:d6:
+ 0b:bf:cc:63:5e:ff:af:7b:c4:ef:48:5d:ef:c7:44:bb:46:be:
+ cb:3f:c4:3e:9c:f2:98:9e:ec:7b:20:4a:54:a0:cd:2d:3a:cf:
+ a0:2d:4a:db:2b:97:50:82:92:68:4e:f0:f1:73:b0:b8:6f:62:
+ d6:97:95:15:75:03:41:5a:d6:f2:f8:a3:eb:f6:48:49:44:49:
+ 93:9e:6c:2c:e1:a1:d5:e6:fd:f2:d5:d9:ee:f0:86:ef:01:49:
+ 65:d6:fd:ae:9d:3a:a6:1d:1a:b3:9b:09:8a:49:cb:ff:b9:d2:
+ b7:16:7e:f5:36:05:c4:09:c1:b1:49:97:0d:8f:22:c9:8c:9e:
+ bb:ea:ce:f9:a5:57:47:de:04:7a:bd:e4:b6:a0:0b:bc:d1:da:
+ a1:20:e1:5c:c4:7a:13:fa:7c:c3:dd:a3:b2:48:98:29:83:75:
+ c1:6d:f8:1b:75:7c:f4:91:ae:66:d9:bf:49:3e:4c:85:01:df:
+ d7:8a:1c:44:9c:d0:d1:4f:7f:c2:0f:7d:f8:e0:11:4b:b6:b0:
+ 4e:6f:1a:f1:fb:53:f8:42:c8:54:15:6b:29:a9:b1:1c:f3:f4:
+ ed:52:2e:66:e9:49:1a:d0:52:66:6e:fa:8d:ff:2f:1e:d3:40:
+ c7:25:b7:69:1d:d4:a9:4d:ee:82:14:32:0e:82:ed:94:b6:02:
+ b0:00:d1:b3:20:f2:eb:9e:db:0f:c9:4f:3d:ec:b2:6e:bd:41:
+ d2:93:87:1f:fb:8c:b8:0e:e3:6a:f4:37:dd:4f:a0:24:e6:22:
+ 3d:c7:a2:46:06:bc:5e:25:74:aa:ae:fc:de:94:d9:5d:6f:1b:
+ 0f:e3:99:46:06:4c:20:ff:59:d1:ef:75:76:87:c8:9a:7d:97:
+ 67:6d:06:d8:20:83:84:1d:c4:72:43:8e:e2:ee:3a:d9:21:59:
+ 63:c9:a0:56:d1:42:f4:34:fe:ed:ac:a4:0a:28:0a:c0:a0:32:
+ 0f:f1:4d:36:b0:27:9c:a5:88:6f:b2:65:a9:43:d2:09:0c:ce:
+ 06:b7:1b:e4:44:d0:e3:cf:da:2f:11:df:cf:83:3a:8e:04:7e:
+ 5f:31:90:c1:1e:1c:b1:ca:4c:21:4d:6a:7a:c5:e0:17:61:ee:
+ c5:61:dd:0d:41:71:96:77:6d:a4:43:a1:69:60:22:56:ea:b1:
+ 88:b1:71:53:79:44:25:f2:42:c9:b3:48:f7:6c:9a:6a:be:f1:
+ ca:88:3e:14:b9:69:9e:57:0b:f6:b6:1b:c9:88:70:7c:a8:80:
+ d6:34:9f:63:59:48:86:4c:bc:eb:cb:49:1f:ad:f6:13:b0:de:
+ c1:0f:8f:a8:0c:f3:ce:f4:56:b5:3c:1d:82:df:9a:98:4b:5f:
+ 91:1f:56:f3:ad:b7:45:92:9f:44:d1:5b:63:da:1a:96:cc:db:
+ 9f:48:64:08:19:29:3f:a4:27:6c:ed:20:49:07:dd:b0:72:9e:
+ fd:59:37:73:69:07:1c:24:c4:46:5d:58:d1:0f:e4:05:08:09:
+ 60:b2:8b:df:a2:a5:07:99:c9:4f:cb:f3:d2:d8:bf:b7:0a:6e:
+ cd:73:43:a5:a0:46:1a:f8:c1:88:01:e2:7c:c1:a7:36:ad:f3:
+ d1:28:e5:4c:b7:5b:a3:08:70:0e:3a:d9:d3:ce:55:0f:2f:f8:
+ 08:7a:3c:78:0c:c0:ca:1d:8f:35:34:6d:8f:10:3b:3e:ad:3f:
+ 2d:55:9e:d0:aa:e7:43:39:c0:64:4b:55:f9:62:a1:f4:1d:90:
+ 4a:3e:06:1d:0b:8e:12:18:6e:28:30:92:88:80:99:6b:bb:a2:
+ f2:2b:79:1a:4f:53:e5:4d:10:bd:3b:81:9d:1c:9c:06:a1:a4:
+ df:61:ec:5a:05:82:38:91:84:65:f6:74:83:9a:28:eb:23:92:
+ 1f:ef:77:77:30:98:c4:90:45:d7:4e:80:fb:e7:50:52:0c:03:
+ 10:0d:b3:e4:0a:20:6c:85:fd:2a:b6:87:e2:7d:69:b0:76:ac:
+ b6:23:ff:09:6e:ab:7e:98:3e:72:76:bf:a1:e2:77:32:2e:51:
+ 85:db:cf:d8:50:b3:e2:c6:7c:75:bf:67:3a:76:c0:d5:78:2b:
+ 1e:c8:ee:63:31:20:7a:33:c9:90:df:8e:95:d7:be:04:32:93:
+ bd:60:46:2d:18:52:0d:e0:99:1f:da:1e:d1:ae:f1:ea:a8:7a:
+ 90:80:7e:10:89:51:c5:a2:55:38:b8:59:12:92:9f:93:87:e3:
+ 0f:bb:02:29:ed:75:8f:8a:17:8d:81:91:65:78:a8:1d:9d:2d:
+ 1e:0f:9b:53:d4:13:8d:55:92:eb:8a:45:19:f6:6b:44:03:47:
+ 9c:3b:18:1b:d3:f1:a1:3d:c4:66:b0:16:d6:60:26:b3:8d:89:
+ 52:27:29:4e:3e:b8:50:fd:64:20:fc:02:58:e2:83:6f:15:35:
+ e4:a6:c8:d6:2c:e3:ce:e2:9d:d1:53:a6:d9:57:90:63:24:ab:
+ 18:d8:6e:63:90:9a:eb:9b:80:c2:98:8a:9a:bf:20:92:b3:36:
+ a0:04:fa:88:2d:d6:0f:3f:9c:4f:4d:21:96:c7:4d:2e:c0:34:
+ f0:40:d0:f7:f3:ea:c5:57:cd:3f:ec:74:73:5e:f9:b0:7e:71:
+ f3:7c:6e:6a:3c:a1:22:b0:f4:40:d8:62:08:a3:97:8c:14:66:
+ ff:bb:83:ab:26:24:01:9f:b5:03:0e:80:5e:e9:e9:f1:14:84:
+ 97:4d:f8:06:db:98:2d:8c:93:e2:af:45:44:7a:d5:61:bb:d6:
+ f1:c4:5e:38:62:3e:1b:1d:d6:69:42:49:66:a0:83:da:23:5d:
+ 42:b9:c7:a0:07:5a:ef:e7:b2:9c:4d:83:63:6c:89:61:c7:d6:
+ 29:4d:76:6d:eb:26:6d:25:c9:fc:5d:2d:58:88:17:5e:a5:4b:
+ 7f:0b:6e:c7:7d:f8:6a:50:53:c7:80:35:98:76:31:cd:f0:2f:
+ 6c:f9:79:93:c4:bb:9b:01:89:00:df:0f:55:c5:67:19:50:b2:
+ 31:ca:68:f8:30:f6:0a:88:2d:e8:55:0d:af:cd:5b:cd:f9:3a:
+ bc:3c:87:f1:32:b5:f3:01:a9:09:44:ea:54:2b:18:33:05:d3:
+ c6:b8:56:e3:5a:70:38:4c:98:5f:ee:17:6b:12:2d:00:fa:c1:
+ 33:48:73:7c:29:51:9a:44:c7:74:b6:63:2f:b4:f8:87:3c:e4:
+ 44:4b:5d:0f:ec:b1:ec:56:b2:23:da:d4:1f:b7:a9:13:d9:1d:
+ b3:d6:19:d6:23:fc:72:9a:15:d6:6c:df:66:71:55:a6:89:9b:
+ ed:ef:25:ee:fb:2b:b3:58:59:d0:42:ab:da:97:88:de:cc:06:
+ aa:b2:85:d5:f0:a2:87:0e:cb:58:7c:14:9d:d1:8e:d0:21:9b:
+ 80:67:ae:73:4c:34:fd:42:36:71:dc:a5:55:4d:79:19:dc:4c:
+ f2:bd:76:25:e3:f0:88:62:4f:79:4b:e0:2f:30:cf:40:42:4b:
+ 54:1c:85:23:11:66:3f:23:6a:dc:54:f3:5b:cd:9c:b6:88:e4:
+ 71:87:5c:2f:7e:85:55:5b:9c:23:3a:00:1f:57:da:b6:33:06:
+ 8d:ce:07:89:b9:2d:7d:0b:35:8d:8a:8b:66:cd:59:16:55:db:
+ b0:b4:2d:3d:53:32:03:90:98:55:a4:f9:8d:f3:84:b4:16:06:
+ 42:3e:ca:71:ef:db:c7:59:32:fa:e3:ec:3b:fb:c9:83:83:90:
+ 52:99:06:c1:b4:3d:ea:f9:83:fa:a8:d9:c3:09:8b:15:d3:85:
+ 06:a1:e5:26:93:4d:23:aa:cd:4b:3e:5f:64:8f:2f:71:7e:c8:
+ d3:8c:78:ef:79:3e:ab:4c:da:77:23:2e:ff:56:35:8d:27:7d:
+ 7e:7e:df:03:1a:c9:c2:af:f4:62:49:f7:35:1c:6a:9b:19:f9:
+ 0a:e2:17:36:b6:6f:d6:ee:b6:e7:12:e3:c1:54:17:22:6a:28:
+ 6d:cd:9c:e0:ef:b7:1c:99:e1:94:53:8f:c5:55:65:b2:f0:4d:
+ 0f:c7:ef:d0:0a:b4:9f:c2:c9:9d:af:26:2e:98:5e:cd:59:79:
+ 80:1a:28:27:68:fb:22:f3:14:71:60:79:a0:5f:92:75:59:36:
+ 58:fd:80:20:07:e9:8f:34:5c:38:53:7b:4a:d1:58:77:af:1b:
+ 0a:0f:70:3f:db:b8:58:37:01:e6:ef:8d:3c:e5:c7:71:c6:bd:
+ 1a:0b:12:33:5f:9d:07:36:4d:7d:02:07:85:60:83:6f:fc:e0:
+ af:9f:c5:22:4b:cd:89:dc:2f:b3:53:e1:7f:b9:65:af:59:57:
+ 54:87:32:f2:d6:15:88:e2:2b:a7:20:85:42:26:d9:09:ee:87:
+ 60:0c:bf:a3:6a:e5:e5:db:6f:2c:05:7e:88:ee:aa:98:d7:6d:
+ 0e:3c:9f:6c:28:70:02:1a:78:b6:2e:2c:29:9e:d9:fa:1e:80:
+ 0a:a3:e6:dc:46:00:15:e7:c0:13:36:42:9c:f0:1a:fa:9c:7d:
+ d0:cd:2f:ce:dd:95:54:fb:b1:fa:bb:3f:83:26:f7:54:42:5d:
+ 60:7a:d1:bd:b2:17:9a:e0:02:a7:9a:d8:40:f4:2c:79:c0:4e:
+ 4e:a4:68:aa:33:c9:1f:2a:f3:fd:9b:50:39:37:b9:1e:59:2c:
+ 7b:cf:97:57:23:81:b7:07:76:8e:b1:f5:15:dc:54:c5:f9:f9:
+ c6:13:b7:37:2d:12:44:c6:78:1e:92:1f:ed:85:8f:14:7a:9f:
+ d0:1f:02:8a:f1:5e:8d:05:28:13:2c:cf:1f:41:9d:74:ec:98:
+ 96:51:57:a7:c0:10:9a:be:31:7d:71:a7:c2:38:cd:ce:74:60:
+ e4:45:ef:b0:60:87:7a:da:e9:05:38:06:19:fe:60:db:c6:2d:
+ a4:25:f7:5d:d1:b1:3d:12:0b:a0:fb:3c:80:b0:f5:00:48:34:
+ 8b:e4:a4:f1:cf:fe:be:a2:38:43:44:20:23:5e:89:c8:91:18:
+ fb:5e:aa:90:11:07:7a:e8:ad:61:03:17:6a:a5:a6:a9:e2:c4:
+ 8c:f0:5e:d7:25:fc:43:93:db:47:44:c1:25:26:38:7a:17:08:
+ 24:36:d3:25:95:eb:c3:17:ca:b5:d7:79:73:62:48:d6:d0:af:
+ e7:a0:61:83:5d:7a:27:e9:b9:7e:d2:25:17:85:c2:28:5b:9f:
+ e7:01:67:f7:69:83:eb:ec:cd:8e:2b:ee:f1:4c:17:a5:3e:05:
+ 2c:c1:eb:b7:64:d8:fa:3b:3b:c4:e4:53:18:fd:d8:da:1a:f4:
+ c7:c2:47:90:91:ab:3d:2b:12:1d:9e:7e:58:7e:cb:63:9c:ff:
+ 28:bf:a0:c3:a6:07:52:58:9a:de:2c:4c:63:5e:79:8b:21:cb:
+ cf:5b:08:7d:6e:44:55:32:5c:37:71:46:02:c1:28:e8:5f:c1:
+ 34:ca:b9:25:0a:c8:88:a8:bc:13:1f:1d:c3:6f:cd:3e:f9:95:
+ ad:45:7b:f9:03:05:b8:f8:c8:89:fa:a0:7a:2b:b7:15:ba:d2:
+ 0a:39:39:c9:0e:0d:5b:f9:11:b2:9e:11:98:90:bd:25:9d:a2:
+ f2:c8:c1:3b:70:a3:71:b9:9c:46:24:b1:00:1b:54:3c:ba:11:
+ a2:73:d4:bf:85:08:57:8f:05:6f:9f:23:12:ff:06:73:d5:6d:
+ 13:7c:1b:50:c4:df:8e:8a:8a:f8:f4:5c:61:c1:51:e8:d3:82:
+ 7b:ef:60:89:56:4c:fa:e1:b5:50:47:23:66:7e:af:af:a7:9f:
+ 0a:1f:55:2d:d9:ad:8f:92:01:d4:10:c8:00:63:0e:6e:95:1b:
+ ea:e9:d3:38:ac:7c:34:d1:8d:61:e6:be:c5:98:3d:9e:cf:70:
+ 28:5c:4f:80:44:4d:06:9d:1e:e0:4b:4c:91:76:7e:d8:ff:5f:
+ 5a:ae:b6:88:f6:25:7b:96:80:4c:59:3f:91:29:4f:44:c1:01:
+ 23:9c:8c:51:f1:fa:69:e0:b1:2a:20:c8:93:14:f4:99:61:a7:
+ 4e:9b:ee:8d:02:5a:e2:29:d7:f5:9f:ef:94:59:4c:55:c6:0f:
+ 1c:b8:9c:a9:b6:bf:cd:c4:30:e5:e5:84:4e:d3:13:af:6e:8f:
+ 8d:ac:d4:64:c0:21:5d:57:e7:9d:73:c6:90:63:b7:22:3e:a6:
+ a6:47:d3:be:b4:a3:ff:c6:c3:0e:2b:f0:45:ee:50:50:98:b2:
+ 8e:f2:bc:05:d0:24:b5:40:b2:52:b4:bf:d7:62:8a:a2:a8:d3:
+ 6c:3e:ad:12:46:ab:5b:c7:e1:32:99:21:7c:cb:09:37:2e:8f:
+ 0b:85:41:5f:45:8f:17:ca:66:6a:73:97:73:3c:3d:8d:dc:9f:
+ 51:dd:59:15:0f:68:4b:a2:42:4e:98:6d:1c:74:9b:5d:b4:65:
+ ed:22:17:a4:73:b2:11:93:f4:d2:b5:aa:93:00:c5:88:42:86:
+ b5:1c:3f:fc:9f:f7:f9:2f:a9:29:a5:66:e2:e5:8e:29:ef:d5:
+ d8:dd:93:62:26:08:7a:f6:62:6c:17:1c:b0:d6:19:b1:39:8c:
+ c4:54:a5:ef:34:b6:15:53:fd:88:80:eb:48:50:0d:74:8e:34:
+ 04:64:f1:da:13:4f:df:63:7b:c3:19:be:c4:1f:f2:13:25:cb:
+ 73:37:98:93:4f:8d:ed:97:88:ee:be:39:9f:03:bd:2a:4f:08:
+ 1d:63:19:dd:20:06:6c:4c:96:a7:66:82:7f:75:62:19:5b:87:
+ cd:76:e3:b9:10:76:ab:b4:bd:54:ca:3e:10:15:a4:36:42:93:
+ e9:56:fa:19:59:e3:90:55:ad:f7:b2:07:cd:99:9b:7c:05:01:
+ a9:d1:fc:eb:e6:d5:de:01:39:ac:0d:be:c7:db:fb:b0:54:08:
+ 48:26:f9:54:dc:15:85:a2:6f:33:10:60:ec:0d:38:ad:15:e4:
+ 14:83:3d:65:df:0b:ae:01:f0:15:9a:ca:bc:4f:c9:c0:57:9f:
+ 9a:b9:22:53:56:b3:4c:83:50:f6:5b:13:68:94:6e:12:7c:bf:
+ 9e:22:6d:c3:17:f4:f0:a5:73:b5:03:ef:b8:af:0e:20:c9:df:
+ 2d:39:e6:43:38:84:30:6b:a1:36:0a:bf:09:41:67:18:85:8e:
+ 77:04:a3:be:d4:9f:36:75:42:e5:84:ea:75:fa:0b:01:4a:30:
+ 48:9c:b3:47:9d:3c:48:41:74:76:14:30:dc:66:1e:fd:7c:58:
+ 2f:63:e5:74:2a:c5:a2:26:65:fe:c2:13:7b:0d:83:f7:94:3d:
+ 2d:f8:83:07:aa:dd:77:72:26:fe:70:29:d5:1e:83:21:ba:0e:
+ de:10:ba:4c:cd:19:88:b2:9a:44:f8:86:d8:fa:4a:12:3b:7f:
+ 02:ae:af:68:7a:87:8b:8f:63:e5:9a:da:e4:77:9d:40:50:27:
+ 7f:b0:7a:ac:5c:aa:b4:33:8a:dc:fe:17:74:3e:1d:5d:5c:0a:
+ 57:06:72:ac:ac:ed:2d:81:09:b3:6e:71:25:3b:03:29:67:b5:
+ 81:bd:b3:3f:36:42:f3:af:ac:db:58:fe:00:42:b4:28:89:1a:
+ 76:fb:11:8a:32:c9:65:03:f7:ef:6a:e6:57:48:e9:b9:d2:90:
+ 42:9a:cc:ff:f1:fb:06:ad:be:ff:4e:c2:64:93:39:48:4a:57:
+ e6:06:98:14:9f:0c:61:19:c1:f0:d4:c8:3d:1f:6c:a5:c2:28:
+ 66:25:aa:7a:9f:fd:08:f6:ec:c1:e5:f8:e2:37:17:f5:6b:ae:
+ aa:f7:a4:da:27:59:25:0e:43:4c:53:62:3b:62:6a:5e:6d:9d:
+ c9:01:02:f3:79:47:0e:2d:ea:62:89:a5:57:6d:de:18:b2:a2:
+ 13:d2:34:27:28:4f:4a:a5:d7:f3:ee:16:a0:5e:77:f9:2e:6c:
+ 22:81:44:ae:03:64:e1:9f:b8:d7:55:92:75:f6:66:ea:2e:45:
+ 31:16:aa:32:77:3f:8e:0e:b7:4a:d9:52:ad:05:28:d1:58:8a:
+ d4:79:b0:ba:29:15:33:5b:99:59:d2:70:95:fb:6c:0a:5d:18:
+ 4a:96:ab:14:96:c8:00:9c:5c:e1:f2:f6:ec:85:e4:3e:39:39:
+ b7:44:3d:9a:66:bd:d9:32:48:c8:5c:0c:d0:4c:b9:2d:01:49:
+ 1d:eb:fd:41:32:67:23:87:10:31:17:20:df:f1:3f:77:ed:b8:
+ d9:52:dc:3d:11:15:dd:04:93:88:f2:65:32:6d:7b:c0:d8:df:
+ 3a:19:61:9b:7f:e5:a2:b1:e0:6f:6a:3d:f5:82:08:a1:0e:26:
+ 42:e2:91:9f:c2:a2:7e:ba:66:89:a7:95:26:c5:9a:9a:7b:2e:
+ c7:cf:4e:70:07:2b:f6:f0:56:60:f4:56:ba:73:18:e6:b8:85:
+ 96:35:b2:d4:3d:3e:63:ca:be:08:54:f3:7c:03:e9:6f:98:17:
+ f5:03:66:da:dc:bc:4a:a8:97:d4:ff:74:89:ba:1d:22:91:58:
+ 50:87:43:c6:1f:ef:0f:cb:9c:da:0e:f8:6b:1e:ab:84:17:f0:
+ 56:17:dd:4a:19:62:4f:d6:56:ef:27:ba:81:ff:fb:5e:c6:4d:
+ a1:20:03:a4:4b:30:d1:09:37:8a:e3:34:cb:2e:5d:9e:6f:a4:
+ bb:9c:0d:7c:b0:40:66:23:7d:ac:dc:b9:7c:68:f4:db:f7:92:
+ c6:db:dc:dc:77:5c:d7:d3:57:a7:13:60:a3:ed:87:15:da:fe:
+ a5:f0:2e:46:94:ad:92:75:61:f2:a8:08:79:83:af:ea:f5:7a:
+ 4d:02:2a:f2:39:d6:87:ef:4f:17:49:06:86:5a:54:9d:48:0e:
+ ab:7b:b6:f7:eb:68:85:83:01:52:d5:a8:32:fe:31:c0:45:11:
+ e2:dc:d6:99:d6:27:90:b5:41:48:b0:39:da:9e:0e:d2:6d:48:
+ bb:c4:0a:15:53:8b:16:77:9a:b9:62:b9:18:db:ea:25:17:8f:
+ c4:18:cd:1c:93:33:0e:9b:e0:ef:da:22:d8:55:17:2d:90:f1:
+ 5e:35:a0:24:eb:3b:d4:50:45:d9:35:3b:0b:c3:d4:6e:67:b2:
+ 92:0e:e6:a7:14:1c:09:e4:d0:94:d8:05:b4:e4:9d:20:5f:f1:
+ be:ca:25:00:34:76:35:e4:3a:19:05:ec:94:8b:9f:cf:c1:1a:
+ 89:bd:8b:1b:8d:68:85:2c:aa:d0:95:3b:d7:b5:47:39:03:7b:
+ 73:38:16:8d:21:68:1a:d8:77:49:1b:02:d5:41:e9:f9:a1:da:
+ 40:75:f1:0a:ab:76:19:79:dd:34:28:6b:4d:d1:44:79:97:a3:
+ fd:15:1e:12:77:ce:e9:04:12:c3:47:75:5e:73:5d:8b:50:8e:
+ 7b:78:74:a2:10:44:dc:29:ec:bb:09:04:98:dd:9a:98:a1:f8:
+ fd:c5:5d:41:4c:9e:13:33:09:f7:59:d2:ca:7b:86:8c:0f:f0:
+ 8a:40:fb:6a:7d:c9:5b:18:71:28:15:e9:8a:97:b8:4e:56:a1:
+ 3c:82:69:f2:1e:05:b8:4e:b4:0f:c5:11:b0:d8:cd:e1:07:5e:
+ 39:a1:e4:a0:ad:19:c7:af:3b:a1:3c:01:69:8a:4d:8e:13:22:
+ 53:98:ab:9c:a5:2e:75:1c:97:d9:0c:e7:0b:d4:79:41:2a:2d:
+ 73:97:45:e4:b1:4b:37:54:d7:90:2f:36:f5:49:82:d6:41:e3:
+ 9b:1b:ee:bb:19:f3:a9:5f:74:da:90:0f:ef:c3:89:e7:21:cd:
+ 0a:51:71:aa:77:76:ad:e2:15:f1:ac:cf:68:34:09:d3:00:4c:
+ bc:c9:58:9f:b5:6c:e6:f9:4a:cc:3b:73:7a:0a:02:8d:da:98:
+ 77:14:34:98:5d:ef:c0:8e:71:bc:b2:dc:56:1f:ec:b0:b0:46:
+ 75:23:7b:00:33:44:f3:85:a7:49:81:47:28:62:76:4d:91:3e:
+ 57:5c:58:31:a2:b1:de:0b:49:fb:92:ad:d1:a7:1d:61:0d:03:
+ ef:7c:fd:9b:9a:36:ce:b1:1d:37:87:3c:ea:3e:54:37:b5:de:
+ ea:e6:79:27:aa:4f:2f:94:17:21:09:24:5d:33:c9:ea:2d:e8:
+ 87:f1:6a:d3:d6:fd:85:79:bd:66:25:2b:e6:0d:d6:23:3d:82:
+ d8:28:4a:31:06:7a:b6:79:3a:2a:d2:a5:00:5b:9a:f3:3f:15:
+ c4:cc:f5:6f:00:f3:77:b1:c6:31:9b:7d:33:eb:0e:b0:0f:f3:
+ 9d:4e:49:85:1d:90:01:15:fa:3d:08:7d:27:06:ea:92:3e:b0:
+ c7:93:da:48:70:1a:1f:7e:52:f8:4c:63:c2:d4:82:cc:40:44:
+ c0:84:38:fc:70:ad:c9:16:41:80:73:8c:8e:6f:dd:e9:48:27:
+ 8c:c1:87:c1:ca:4d:14:66:53:e1:ce:9d:1b:3a:16:14:cd:dd:
+ ce:12:a9:2a:ba:c9:4c:34:4b:4a:85:1f:9d:d4:6a:70:e9:df:
+ 18:9f:03:d2:bf:73:f3:e0:b3:8f:d2:10:09:97:fd:59:fc:50:
+ c8:57:a7:77:34:43:82:79:b5:56:f5:33:c2:a0:dd:2e:78:09:
+ 60:ee:0a:92:d3:13:5c:b3:41:79:df:fc:07:09:a5:54:aa:e5:
+ 59:6a:30:d0:f1:ed:bb:f1:5a:52:61:9d:06:36:dc:c8:80:70:
+ 77:45:05:b9:2f:4d:c4:86:fe:d7:51:51:93:88:8b:66:1c:fa:
+ 0d:ce:91:5e:e0:25:3b:6f:89:47:df:52:09:b7:0e:c1:82:90:
+ 96:17:b0:8c:af:d1:2c:00:b0:bb:18:5f:34:56:d5:f6:18:00:
+ 56:4c:09:ab:32:77:e0:35:56:1a:52:99:1a:d9:e1:ce:75:9f:
+ 24:4c:7d:e5:34:4f:52:1c:d4:6b:93:b8:a5:c5:68:97:68:a5:
+ 1d:1f:77:16:2d:4a:29:0d:e5:2b:9a:f9:05:2a:1d:3a:f9:7e:
+ 45:3e:a0:5f:88:d6:8b:bd:47:f3:a7:7b:a1:27:29:1c:3a:26:
+ 41:b8:fe:80:53:6d:29:64:09:1f:6f:e9:ac:ff:70:8f:06:ef:
+ 48:a2:07:1f:8c:d9:86:2c:84:f4:a7:01:66:d1:2f:f2:47:92:
+ a1:e6:de:a7:28:70:4f:bd:d6:d8:b4:ed:17:ab:4e:18:4f:e2:
+ 4a:62:d0:7f:30:9c:40:2e
+-----BEGIN CERTIFICATE-----
+MIIg7zCCAimgAwIBAgIUZUd1tFj6suEfFrVfJUm0fyoSYFYwCwYJYIZIAWUDBAMU
+MIGiMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96
+ZW1hbjEYMBYGA1UECgwPd29sZlNTTF9TTEgtRFNBMRowGAYDVQQLDBFSb290LVNM
+SC1EU0Etc2hhMjEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMR8wHQYJKoZIhvcN
+AQkBFhBpbmZvQHdvbGZzc2wuY29tMB4XDTI2MDQyODA4MTAwNVoXDTI5MDEyMjA4
+MTAwNVowgaIxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAwDgYDVQQH
+DAdCb3plbWFuMRgwFgYDVQQKDA93b2xmU1NMX1NMSC1EU0ExGjAYBgNVBAsMEVJv
+b3QtU0xILURTQS1zaGEyMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xHzAdBgkq
+hkiG9w0BCQEWEGluZm9Ad29sZnNzbC5jb20wMDALBglghkgBZQMEAxQDIQCm4CSo
+ShgQTc3G2iMkZ96WlYEApsrOQ+T9/HsajPrrfqNjMGEwHQYDVR0OBBYEFL2AIzoG
+3ThX7mvClHvqv0NXOrCMMB8GA1UdIwQYMBaAFL2AIzoG3ThX7mvClHvqv0NXOrCM
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAsGCWCGSAFlAwQDFAOC
+HrEAEDxwxfWxTxx3hTS5s1eXYu/nTRdnVmN0AuFQXFvcVpRF1/lrdv374gAWC1oP
+hoZ+HgxOf9CVy9SNCVhTTuKfDhEuONzAh+xvJe8bWYGrcWlRLjwv9uvyInNi4aJo
+s2fbLgUW+ryutiLLwg/9iB42QycT3mEBDLpa3wppF0XCd7Xfh2KzFm6OV+A7nwKA
+XfCSdlFzLnolHIh53Q1VxXOUs3ZSOftYDTT+dDhF/Jk5h8f+Syp8Ua6S7l8oFhME
+tfBfk5B00OD0HQava60z/izVmuUQMn8BL36Xxv+2V5dWzFpamnneGbCcC1e7u+WL
+kSzNGS18dY5xT8XFiHRcXSeC3JRYfW5xbnjF8w07hZUq2k2vNKk8AojMRR0IDJ0g
+OXMGCi26il2pRDIkMrHR/dF/tBBWOhUSpfbBbxbvhF6Gq1ubp7Qh40OGGlD4lbOw
+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
+-----END CERTIFICATE-----
diff --git a/examples/certs/slhdsa/root-slhdsa-shake-128s-priv.pem b/examples/certs/slhdsa/root-slhdsa-shake-128s-priv.pem
new file mode 100644
index 00000000..6903f52d
--- /dev/null
+++ b/examples/certs/slhdsa/root-slhdsa-shake-128s-priv.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MFICAQAwCwYJYIZIAWUDBAMaBEAnqyIgk6VNnF0xM5x6Vbnp9D23c/kPGQeocOR8
+HctLAiqMjF2YEIFMAUeaq+RxTydF4qwQjpWAlJQwoaItuSqz
+-----END PRIVATE KEY-----
diff --git a/examples/certs/slhdsa/root-slhdsa-shake-128s.pem b/examples/certs/slhdsa/root-slhdsa-shake-128s.pem
new file mode 100644
index 00000000..efbc9ed3
--- /dev/null
+++ b/examples/certs/slhdsa/root-slhdsa-shake-128s.pem
@@ -0,0 +1,644 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 1a:b9:a5:11:e8:b5:2e:6d:06:db:c8:39:df:1a:50:04:21:1e:f9:44
+ Signature Algorithm: SLH-DSA-SHAKE-128s
+ Issuer: C=US, ST=Montana, L=Bozeman, O=wolfSSL_SLH-DSA, OU=Root-SLH-DSA-shake, CN=www.wolfssl.com, emailAddress=info@wolfssl.com
+ Validity
+ Not Before: Apr 28 08:10:04 2026 GMT
+ Not After : Jan 22 08:10:04 2029 GMT
+ Subject: C=US, ST=Montana, L=Bozeman, O=wolfSSL_SLH-DSA, OU=Root-SLH-DSA-shake, CN=www.wolfssl.com, emailAddress=info@wolfssl.com
+ Subject Public Key Info:
+ Public Key Algorithm: SLH-DSA-SHAKE-128s
+ SLH-DSA-SHAKE-128s Public-Key:
+ pub:
+ 2a:8c:8c:5d:98:10:81:4c:01:47:9a:ab:e4:71:4f:
+ 27:45:e2:ac:10:8e:95:80:94:94:30:a1:a2:2d:b9:
+ 2a:b3
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 62:90:90:E5:3A:74:18:1B:FB:68:40:07:A5:83:9D:70:2E:7E:C9:F0
+ X509v3 Authority Key Identifier:
+ 62:90:90:E5:3A:74:18:1B:FB:68:40:07:A5:83:9D:70:2E:7E:C9:F0
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ Signature Algorithm: SLH-DSA-SHAKE-128s
+ Signature Value:
+ f3:00:21:12:66:43:0d:fb:2c:be:12:be:ed:07:4f:ae:78:1b:
+ 4c:cf:e2:e8:85:cb:e4:68:f0:6e:6e:63:78:e1:10:0c:3c:7d:
+ 0f:57:03:dc:ac:73:81:7c:c9:5e:77:e2:d0:77:0a:b1:7e:03:
+ cc:3b:f6:a4:19:e4:e1:dd:89:d5:dd:cf:0b:c2:17:a0:fd:fd:
+ 08:d8:d3:c8:12:1c:a8:de:bb:a0:a7:ba:81:bb:c9:b0:bd:09:
+ fd:05:cf:f8:89:5c:38:d7:0f:a6:8f:85:98:cd:3e:a3:c1:9a:
+ e9:4c:6b:bb:0e:25:fc:3d:83:19:53:8a:38:ff:e8:1b:da:cb:
+ 35:24:3e:1c:de:8c:9d:83:f7:2a:9e:52:4b:1b:0a:14:e6:b4:
+ 1f:5b:de:0d:3f:68:8c:ab:e9:da:d8:61:37:c2:c0:e3:28:b2:
+ df:41:0e:d2:90:30:e2:f6:a4:18:a1:1f:43:c4:30:a3:68:9d:
+ c1:d6:81:d1:2a:2e:a6:6a:7d:c7:a3:6b:4f:f1:6e:67:5d:b0:
+ 08:4b:9f:0f:c1:98:4f:5e:13:80:44:d7:c0:d7:32:d6:af:42:
+ a1:cb:c0:86:f3:d3:db:17:89:ed:f1:bd:af:f3:eb:76:f1:2a:
+ 9c:0c:d9:c9:86:8b:46:f8:0a:cf:5f:e7:fb:50:a1:69:e5:5e:
+ a5:78:81:63:5a:9c:2d:a4:b2:2b:d6:fa:c1:f9:7e:7a:01:39:
+ b1:0f:17:b2:e6:74:b0:01:59:59:14:f6:49:6b:f9:63:4a:84:
+ 32:83:80:2c:cf:8e:4e:0b:f6:ff:41:47:6b:c2:f7:54:cf:44:
+ 09:0e:12:cd:8e:8f:f0:58:04:80:20:a8:70:db:a6:a8:8c:ba:
+ 3d:97:06:d2:0f:3d:cf:c5:39:80:5b:8a:f7:22:67:df:92:62:
+ 86:3f:e5:8f:fe:0f:f2:e9:8f:6c:ea:7b:ef:2d:c1:db:9c:6b:
+ c5:93:e6:33:70:59:9b:57:3e:3e:b4:0f:1d:97:b2:25:07:5b:
+ 10:05:60:d4:02:4f:cb:5d:ce:69:51:69:86:2e:74:82:c3:02:
+ 9d:1c:77:28:1d:b5:d4:a3:18:c6:9c:59:d4:a8:1d:0e:38:9f:
+ aa:13:10:c3:c2:39:d1:e2:c6:b7:f8:e3:c3:68:9e:3b:8a:91:
+ 41:d0:93:4f:63:06:cf:d7:e2:58:15:19:a3:ca:14:0b:d5:9f:
+ 81:2e:d8:35:29:ca:5e:73:75:e1:bb:30:d5:89:5f:fc:b3:b5:
+ de:5a:86:3e:1f:c0:65:f9:ae:73:ce:ef:7a:c0:68:65:91:5f:
+ 20:22:d8:91:cf:b8:ae:ad:73:bf:df:71:da:9a:fe:28:94:26:
+ 3e:8e:e8:50:4a:86:9a:0c:07:fb:f7:9f:29:c5:06:4b:3e:d1:
+ 0b:0e:8f:a4:f2:c0:db:dc:d5:b7:9e:9f:0f:53:0e:fe:74:13:
+ e3:91:a5:a4:40:eb:4c:43:73:eb:b7:99:4f:de:a1:f2:29:bc:
+ 9f:1e:d9:f3:dd:a7:60:65:7e:c6:74:27:6e:fb:a9:62:fc:c9:
+ 87:aa:b3:97:b6:ad:22:4d:d3:58:13:a4:b7:2c:60:fa:f1:d0:
+ d9:4b:43:d0:53:34:61:5c:1d:48:5a:e4:11:2a:1c:43:4e:45:
+ 3a:7e:6e:c7:05:0f:7d:d8:f3:1f:e2:99:f9:45:df:e9:59:b6:
+ 65:aa:41:78:3a:1f:68:bc:de:3c:2d:c3:1a:1a:72:7d:3f:b4:
+ 24:94:8e:90:f9:a7:2f:e2:86:87:77:88:8f:7b:98:82:1c:03:
+ 99:9d:c7:e8:ad:62:49:e0:e5:51:50:95:67:d7:aa:3b:67:c4:
+ 54:7c:6c:f1:fe:d5:4d:17:7d:8c:99:b4:49:4a:57:dd:9b:fd:
+ 46:1c:33:a1:8f:3f:42:be:56:a7:06:00:be:88:1d:01:38:c5:
+ 28:a8:b4:83:4a:b9:84:7d:cd:3a:7b:6d:78:2e:7f:9c:e4:46:
+ ca:18:9f:aa:d2:fe:24:76:e5:b3:a6:3c:de:af:b8:a2:ae:70:
+ b7:2e:76:13:29:1e:6b:7c:eb:54:d8:2c:45:77:e5:c6:a2:29:
+ a6:b2:8d:54:e2:60:f8:34:21:5b:ec:b1:98:49:fb:1a:0e:cd:
+ 54:63:cb:e3:0c:fc:38:11:6a:e5:4a:57:6a:95:de:3b:70:c2:
+ a5:05:d0:10:1c:eb:c5:39:4a:b0:e2:81:82:41:9f:0b:be:8d:
+ 83:fa:1c:9c:81:e6:bc:86:d1:28:69:5d:cc:67:5a:43:84:2e:
+ 9d:6f:cd:af:f0:79:25:63:73:0a:8f:1d:14:9d:cc:e5:65:e9:
+ bf:09:d9:fe:d3:dc:5a:ab:db:0e:02:1d:f8:cf:51:be:70:32:
+ f1:1e:c9:19:cb:52:cd:cb:60:cb:e1:3d:81:8d:b0:f5:f2:bc:
+ 21:c3:b2:71:7b:4c:41:37:be:43:a8:e3:da:87:5f:8f:b9:23:
+ a3:8c:68:d7:90:8e:ba:cd:05:72:1c:36:82:7b:c5:54:29:b0:
+ 41:d1:a4:e0:f8:c8:3c:c5:f7:26:51:27:c2:87:71:1a:0e:86:
+ 54:2f:48:cf:e1:f8:ab:f1:9f:74:36:be:d1:45:21:13:13:4e:
+ d5:c8:98:f7:51:0c:b0:41:25:e4:cc:04:12:7d:03:11:45:37:
+ f9:12:51:a3:a6:f8:25:d8:e7:19:b2:e5:20:37:71:e3:95:7d:
+ 1e:b5:6e:b8:e8:8b:05:42:f7:d4:97:57:e2:3d:c6:8e:8c:a9:
+ 86:8f:57:c0:da:41:89:8d:07:8c:b3:ce:d7:39:93:b5:6e:e0:
+ fb:59:af:76:88:6f:84:5f:4e:54:6b:42:de:95:4f:17:26:5c:
+ 48:bf:ec:65:dd:73:8c:96:c5:5d:1a:5a:79:fa:67:08:05:c9:
+ 72:2f:1b:1c:37:de:1a:a2:17:eb:d1:61:67:29:56:00:b0:3f:
+ 9c:d7:bc:ea:de:31:ed:2c:09:4b:4a:a6:c5:52:43:d5:38:6f:
+ 82:b4:b9:c9:9d:3b:1a:60:91:8c:b0:93:c2:91:a8:f1:9c:10:
+ f3:a3:9e:53:6b:e7:fd:84:83:ec:23:f9:5d:05:5d:bb:c2:16:
+ 51:ae:aa:f4:2a:53:3c:8c:5d:5e:f7:cd:d7:64:c3:a9:ce:f9:
+ 7b:31:f5:c4:fb:68:e3:97:4c:08:bf:64:7c:58:6d:4b:11:75:
+ 6f:a8:13:4e:24:76:63:47:ef:e9:ff:bd:0c:7c:cc:f8:5e:91:
+ e9:25:b6:9e:b7:42:97:a2:4d:c9:4b:f8:82:10:ea:45:a1:81:
+ 5d:b7:0f:ed:e0:e0:27:d4:88:c9:6e:3f:47:b8:1d:d7:66:51:
+ dc:0f:4b:cc:cb:fb:a8:f3:85:43:60:71:aa:66:cb:66:54:3a:
+ 63:bf:ac:e1:aa:c9:06:e1:4b:26:f7:df:75:a8:bf:45:70:bd:
+ 18:d8:91:58:bb:8a:51:0e:79:58:01:3d:45:05:5e:9d:02:b4:
+ 7d:06:48:0d:59:4c:fa:4a:ee:fe:72:5a:03:ad:5c:eb:e5:44:
+ 04:b4:20:90:5a:79:1c:2f:62:d5:49:89:59:16:c2:75:07:35:
+ 2c:da:f0:99:3d:5b:49:b3:be:ab:1f:2a:00:cd:18:47:3a:20:
+ 8f:e5:ee:d4:b9:e5:9b:af:ef:36:bb:42:f7:80:02:97:e1:e3:
+ 4a:52:e9:95:e4:15:eb:40:cb:c1:a0:11:17:a7:88:98:eb:34:
+ b3:be:56:d0:ae:2d:9c:fe:38:eb:51:8c:fb:17:74:66:b7:f5:
+ 57:b7:ef:c8:e5:a8:9f:4a:87:df:30:8c:f3:f1:f4:33:bb:fb:
+ 62:76:36:68:f8:50:ba:8c:b2:eb:cd:5c:8e:0a:03:d5:4f:fa:
+ 81:6b:64:e5:5b:a1:f1:27:98:26:08:69:dd:21:70:9d:fa:24:
+ 1f:51:a5:b5:a2:48:1f:fe:fb:ce:82:93:c9:73:cc:c3:5b:30:
+ 62:b7:e8:8c:82:26:57:e0:29:a7:97:c1:98:c5:99:84:f5:4a:
+ 99:d3:23:1d:a7:91:1a:ce:71:e1:fd:be:59:0e:da:a9:82:23:
+ 1d:4b:49:5f:05:7c:20:bc:ff:31:f2:c6:3b:7e:c7:39:74:37:
+ 33:db:f0:7a:bd:06:c5:6b:71:01:57:36:14:dd:8a:57:a8:6e:
+ f5:42:5f:1d:87:88:bd:70:6e:6f:94:bb:82:44:a8:b7:a4:f8:
+ c3:59:25:76:b6:8a:e3:2a:15:a2:22:33:90:ba:b8:9b:78:5d:
+ f3:bf:26:b7:a0:47:13:31:b6:00:e1:19:b0:53:25:f8:28:c7:
+ 60:62:b9:2d:b1:97:ae:1a:9d:41:b7:66:e2:d0:09:11:63:ea:
+ 03:27:1b:dd:c7:d6:20:8a:c3:a8:27:dd:b3:59:d0:0e:9e:f7:
+ af:be:7e:30:53:51:a9:79:53:40:31:ac:5d:20:45:0b:62:96:
+ 98:1a:ed:57:2b:3b:65:7a:49:85:6f:15:6c:a5:4b:53:f5:45:
+ 8f:a6:46:d3:8d:93:af:d3:20:be:c9:b9:fa:db:47:48:07:14:
+ cc:a7:1d:7e:65:66:44:f7:42:ce:40:ef:03:1a:96:bd:be:e1:
+ c5:33:8e:ec:72:1a:ca:b5:64:e5:98:29:68:29:27:a9:b7:75:
+ 84:a1:d0:c6:3b:63:18:fa:77:c0:31:00:9c:8f:64:6a:34:54:
+ d3:5a:43:53:d0:35:f5:e2:11:cf:9d:4e:b2:33:01:1c:49:73:
+ 56:af:ae:ab:04:72:e7:d9:cc:8c:7f:1d:23:0e:7d:1d:46:2f:
+ a7:a5:59:d5:a0:8c:31:5b:08:cd:3a:8c:b4:8d:68:db:a6:94:
+ 08:49:02:16:55:e3:3d:49:c9:35:f6:eb:e0:5f:e7:bf:b6:67:
+ 7d:f5:30:57:60:d6:f0:1e:eb:0d:03:44:9c:cb:3d:da:a1:ff:
+ c4:ee:0b:f2:7f:8f:4d:8a:f2:47:13:55:2c:c5:aa:cd:d9:0f:
+ c7:ba:61:f3:6c:e3:f8:35:1a:1f:69:c6:21:94:fa:5b:92:2d:
+ 76:f1:78:16:e9:31:fd:fc:77:b3:57:f5:a6:8c:8d:1c:55:9e:
+ c6:4e:88:d8:57:4b:e8:37:da:a8:72:95:25:63:46:4f:73:be:
+ 60:ff:58:10:36:ed:98:fb:63:07:e1:19:88:f4:73:a3:b3:48:
+ bd:78:22:85:f7:96:b0:63:9f:63:89:46:35:5a:b6:18:61:d8:
+ 67:8d:ba:b3:7e:83:7b:b8:7f:94:fb:93:c5:34:b7:33:d8:5d:
+ d7:fa:d6:8e:4a:6c:54:3c:29:e5:88:36:8c:96:1d:3b:ac:08:
+ 6c:b2:05:d7:71:12:39:eb:90:46:18:fe:a0:46:97:a3:00:fb:
+ 43:8e:b4:50:1a:b5:f3:e2:72:24:fe:45:3a:ba:71:23:c6:8b:
+ 5b:9b:7e:17:fc:52:3c:34:e7:16:f7:07:6c:98:1b:11:c5:8b:
+ 39:2b:97:59:89:4b:2d:7b:27:54:41:eb:bc:20:02:50:70:bb:
+ 6c:c9:34:68:d6:67:07:39:5e:4e:50:df:8b:f5:b0:ca:be:42:
+ da:34:64:48:f2:cc:98:92:f0:66:3f:d0:6e:87:4f:5c:ef:fd:
+ f5:91:b3:52:39:48:70:d0:99:a3:0f:67:d9:fd:9a:6a:f3:5c:
+ dc:44:e0:a4:f8:5c:c3:cc:85:64:7c:ac:6c:17:7f:23:3c:7e:
+ b2:10:86:c5:57:94:26:0b:6a:aa:f9:34:d7:5a:41:80:85:eb:
+ fc:a9:fd:14:94:ed:c9:48:e4:dd:38:a0:21:27:12:c8:44:fb:
+ 43:fe:1a:4a:ab:13:22:b6:9c:17:c9:7f:99:9c:22:fa:59:57:
+ 35:a5:6a:f7:90:67:58:65:b2:7c:6a:5e:43:2f:4f:1a:f6:b1:
+ 6c:33:4f:84:83:ef:f9:cc:5c:0a:9a:2e:11:25:e0:77:8c:d3:
+ 0b:90:15:d1:ff:7d:4b:d2:a6:a9:ba:a4:bf:08:a3:65:ff:b2:
+ 15:d3:b4:e9:86:74:ce:5e:86:c0:f2:0c:d0:09:27:47:48:aa:
+ 89:88:e7:ca:47:13:4f:f3:b8:d7:e4:aa:af:27:73:93:90:6e:
+ 46:fb:9b:b4:3f:2a:d7:bc:fe:1b:9e:32:09:b5:d4:ac:39:20:
+ ab:52:ab:42:d2:7a:89:83:d9:f0:4f:8b:8b:ad:e7:7d:51:82:
+ c3:9a:98:56:7e:0e:51:a9:13:35:a5:7c:36:86:36:e4:8e:e9:
+ d7:84:d1:82:9c:cd:ad:99:8b:11:f7:a7:66:fe:36:47:56:46:
+ 67:af:59:76:f7:9d:f9:3a:f9:3f:10:22:27:4a:6c:cb:32:2b:
+ 59:13:f4:a0:fd:d6:3b:6c:60:91:bd:aa:f3:a5:31:8d:ee:1a:
+ 38:90:19:3e:a3:8d:e2:10:0e:b3:a3:da:7f:75:e2:79:bf:36:
+ 86:16:7c:bc:94:b2:78:57:c5:45:02:6e:99:ad:cf:2d:21:c4:
+ 6c:59:b2:b2:94:7a:e8:46:2c:12:61:99:74:2e:87:3e:fd:25:
+ 62:58:89:20:35:a5:83:3d:d1:d5:5c:e2:17:58:73:9c:f0:a9:
+ 90:13:ef:a0:b0:38:08:a8:46:f9:07:f1:be:31:9c:ec:ac:65:
+ 82:a1:ee:fd:39:5d:7b:6d:53:2c:a6:23:48:59:2b:13:63:16:
+ 93:69:46:42:4f:98:c8:70:dc:10:a7:fe:8a:28:91:6f:26:cb:
+ a8:e5:26:37:75:cc:a4:cd:88:49:4f:10:0d:c2:46:44:68:7a:
+ 58:82:b4:15:78:47:f3:1a:76:af:cd:84:fe:e0:d6:93:1e:f9:
+ 21:1b:d4:a2:13:29:3c:1a:bd:2a:fe:d4:cd:65:e6:14:e1:31:
+ 82:14:bd:9e:09:e2:be:b9:80:8f:11:11:95:72:d9:a4:0d:c4:
+ 6e:24:31:9e:e9:cf:9e:a0:e2:fa:7d:4b:b0:03:25:b2:90:e6:
+ 7e:08:39:37:17:38:a0:93:f2:8c:ff:8c:ee:68:0e:11:74:ef:
+ b5:b7:47:21:32:82:9b:24:47:e0:88:bf:15:61:e2:3e:55:a6:
+ 87:d6:f0:ba:f7:99:75:4d:7e:2e:76:06:8c:fc:9e:f8:bb:3b:
+ e5:74:70:e1:30:0e:06:ad:01:a4:6d:45:c6:f4:55:47:28:45:
+ cd:7a:8f:29:77:28:9a:a3:bd:ca:2d:b4:ff:53:10:a1:c6:b7:
+ 51:59:0b:04:ff:ba:72:6d:5f:43:4e:df:36:ed:ab:d9:d6:49:
+ bc:8c:8e:be:53:84:2e:e3:00:a3:eb:33:49:23:ad:ae:98:7d:
+ a5:b0:49:29:d4:d1:5e:5a:11:5e:01:f4:f4:13:31:5c:d3:d6:
+ b7:08:da:25:87:7d:b5:73:12:74:06:0d:97:52:7f:60:08:42:
+ 2a:af:14:f5:30:89:ac:f3:32:21:2c:e8:16:fc:45:13:89:28:
+ 6e:4a:95:87:fc:30:36:55:7e:65:01:4f:55:52:39:39:25:40:
+ 21:4c:30:dc:d9:6c:5f:58:fe:57:2b:83:d9:a0:db:4f:b3:4e:
+ 02:48:8e:b7:70:9f:eb:0a:98:61:92:72:84:3e:53:7c:c6:f3:
+ 3e:3f:70:3c:c2:ff:98:4c:1b:a6:71:c1:15:f8:e5:03:4f:53:
+ 62:fe:ec:d9:2d:8c:83:e0:a3:42:7d:d9:16:51:04:2d:42:48:
+ 11:4a:62:31:06:d4:02:5d:93:31:21:aa:47:da:c9:c6:49:be:
+ e4:71:4e:57:36:46:94:bf:fb:e3:88:3b:59:77:6d:13:cf:34:
+ 62:34:ba:46:83:64:1e:22:10:74:18:a5:b2:d2:83:11:3a:ad:
+ b8:72:93:48:c4:6a:1d:b5:c2:d9:dd:2d:4f:bf:eb:e6:3f:9f:
+ e1:85:9b:f1:2e:03:5e:b0:f5:dd:8b:33:d6:7c:5e:e4:34:eb:
+ bd:62:dc:80:03:cf:cd:ed:ef:29:4e:ed:e6:e0:ef:d4:1e:6b:
+ d4:a1:bd:b1:23:73:71:f7:ac:7d:c3:eb:e6:37:ab:8d:46:e1:
+ 24:93:92:b0:8d:f0:84:bb:f7:f6:51:19:52:76:94:ce:b5:f4:
+ 3e:5e:0a:8e:e3:9d:14:43:31:4f:14:91:12:9b:81:7d:51:fe:
+ 22:93:28:e8:e1:d7:8b:72:90:1e:44:61:d2:07:17:f5:ef:81:
+ 69:27:ba:a5:a4:ea:2e:cf:9c:04:cf:fd:0f:c2:2d:4d:57:80:
+ 49:bb:a6:92:be:7a:8e:4a:99:b7:d4:d7:08:63:b4:2c:1b:bf:
+ b6:bb:31:6c:13:7f:84:19:7a:e3:c0:57:5e:c5:5f:9a:27:ed:
+ b2:8b:23:65:96:d5:e0:59:ec:50:0e:63:a8:df:7f:4d:dc:6e:
+ 35:d8:32:86:94:88:7a:20:3d:67:67:08:e5:ed:08:fa:37:7e:
+ 84:6f:a3:e1:d3:62:f2:f7:19:f1:ef:73:b5:6a:a8:16:42:2f:
+ 41:7a:e3:66:be:14:9e:5c:22:95:f4:31:17:44:a8:6c:ea:ac:
+ 6c:d6:c6:a9:76:eb:c9:24:1e:93:76:48:f4:14:3a:18:f2:32:
+ 68:f8:9e:cb:53:e1:a0:04:0b:a8:a6:4b:c2:3f:d7:4d:72:3a:
+ 77:34:e9:7c:b6:18:26:bb:e5:e8:d7:1b:24:86:ea:c0:c8:0a:
+ 81:ea:50:5a:b7:3c:ca:7a:bb:7a:85:4f:03:d2:7d:97:44:80:
+ 0c:ac:58:48:a0:33:74:62:69:db:99:75:b8:a7:7d:c4:64:2f:
+ 00:b5:a7:c1:ec:3d:69:e3:c5:b0:e6:47:ac:1b:c0:b2:15:75:
+ 76:84:a4:f7:05:ed:fe:6d:ea:c0:2f:d3:37:72:dc:b2:8b:23:
+ 45:1c:21:f1:6b:5c:af:59:12:b8:92:9f:be:59:b7:70:68:08:
+ d6:e5:30:b2:cc:16:37:35:22:b7:7d:7d:61:2e:f1:81:a3:08:
+ 99:cd:1f:b6:52:f7:f9:53:c2:dc:e4:4e:40:54:59:9a:0a:4b:
+ fd:92:64:59:71:64:dd:d4:94:74:ac:e4:5b:c8:a9:ad:a2:0b:
+ bc:66:45:ad:30:76:bb:32:81:0a:ef:92:00:50:77:0e:b2:61:
+ 0e:63:8a:a8:a8:d1:84:88:ae:52:53:d3:9b:5f:ae:f8:a5:35:
+ b7:40:51:95:ab:6e:ef:84:c4:84:0f:3b:8f:75:33:05:6d:42:
+ 2c:aa:20:da:17:5c:b8:cb:cd:08:54:6a:18:d1:c0:0d:7d:14:
+ e5:fd:64:1d:8f:e7:b6:ab:e3:4b:95:bc:b3:97:f6:e7:c6:23:
+ 86:9f:98:dd:0a:4a:cb:df:02:5d:1b:66:a9:58:f7:3d:b2:ec:
+ 1e:59:53:38:64:ad:0f:57:1b:5a:10:81:3d:f8:22:6d:af:b7:
+ ea:08:ca:b1:98:d6:66:89:a6:db:3a:c4:80:35:18:a3:7e:8f:
+ cc:32:16:d2:6b:15:83:68:15:15:ed:23:7d:f3:c3:df:a8:5e:
+ bb:0c:61:0e:3f:85:be:df:47:9a:58:c1:84:15:a9:14:14:e5:
+ f0:ae:2d:c8:dd:64:0d:db:e1:78:62:7a:2d:4e:31:bb:e4:3d:
+ fa:92:d9:ca:cf:b3:e2:25:79:33:b2:d3:9b:f4:c1:db:fe:24:
+ de:f5:47:cc:9b:d9:47:b6:b2:3c:e9:25:3d:15:5f:e1:e7:86:
+ 66:f1:4b:8c:56:bf:bc:13:db:cd:40:ab:45:8e:ba:f3:f0:67:
+ 9f:46:c6:72:c6:d6:33:7a:26:39:98:6c:13:cc:2a:cf:99:44:
+ ea:68:97:05:8c:dc:b9:45:a4:10:3b:43:66:af:15:44:0f:53:
+ c7:76:6c:b0:59:db:c5:85:c2:54:c9:5b:d0:ae:2b:c4:54:cf:
+ 38:60:2a:ac:a5:8f:65:15:d4:02:b1:a0:17:4b:c1:b5:5f:bf:
+ 6c:34:23:ee:33:f8:82:27:5d:8f:1b:58:b6:f0:b8:0f:dc:cb:
+ 76:19:e9:2d:b8:85:6f:55:1c:64:36:c8:8c:d3:84:c4:cd:f7:
+ a5:86:93:46:0a:73:45:e1:e5:cc:39:c5:f7:60:cc:ca:02:90:
+ 66:e0:ec:23:e9:23:c0:98:fb:75:e9:74:0b:89:9f:32:1a:68:
+ 89:96:db:d8:70:56:8f:85:e2:a8:39:08:fb:c0:53:8c:f2:af:
+ f2:79:0d:a8:d7:f1:e3:1f:fb:77:b6:70:f9:40:cf:f2:4f:5a:
+ eb:bc:6d:e9:f6:f1:f6:01:fe:83:1f:6f:29:d1:19:10:b5:1e:
+ 37:81:c9:0f:ab:5d:d6:34:80:cc:ba:bc:22:76:6c:f0:a3:1e:
+ cd:6d:fc:1b:8d:58:dd:a6:09:ea:3b:2a:ec:47:7b:b9:ae:e1:
+ b0:52:5d:86:92:5c:d1:26:66:79:5b:3b:e7:8e:61:4e:15:0a:
+ 55:23:cf:01:ac:12:e7:6b:13:52:3a:26:8f:a9:34:79:cc:d7:
+ 63:15:f8:9a:c3:5c:62:0e:fe:5a:c2:84:7b:69:fe:98:9a:c2:
+ ca:ef:63:93:86:4c:df:ac:95:31:b4:ed:c3:3d:87:87:03:da:
+ 35:5f:3c:38:27:c5:5e:05:4a:5e:4b:eb:44:ec:9b:be:b9:71:
+ 6d:63:de:14:de:73:0e:11:90:85:b7:8e:41:92:2a:da:7b:65:
+ 06:fc:86:01:3d:f0:bf:0d:45:44:51:36:c5:1b:9d:66:2f:61:
+ 1c:70:b1:81:63:4c:de:13:38:6a:91:ed:32:51:91:ae:92:bc:
+ 6d:4d:76:e1:14:39:88:3c:c5:2d:ee:95:16:b5:ec:54:40:cc:
+ e1:b0:ab:69:6e:3d:82:de:37:d2:b7:3a:be:89:6c:67:c5:c7:
+ 2b:2a:04:d8:08:4e:f2:a0:84:c2:6e:36:20:1a:e6:58:a5:5b:
+ 31:c9:74:ce:78:5f:4f:ef:d2:0d:95:75:f4:16:a7:61:87:17:
+ a7:b6:98:88:2e:8b:0e:fb:8d:f9:4e:6c:56:dc:38:5e:c7:db:
+ 15:86:d0:8f:e2:19:9a:e2:25:76:ac:46:05:22:77:a7:ca:f1:
+ 81:3b:f6:d5:1d:7c:e9:ea:89:fe:15:ea:6b:3c:28:77:d6:79:
+ dd:96:15:89:9b:97:1f:47:e6:95:c0:97:e4:18:3b:02:dd:4e:
+ 62:e1:1e:6b:78:43:e5:49:20:89:33:5b:1a:84:1e:f8:2e:12:
+ 73:61:85:09:b9:54:24:6e:c8:ee:b9:fa:52:78:4e:27:fb:39:
+ 02:59:09:46:f9:d0:be:03:78:b0:f2:17:80:72:25:ae:a3:2e:
+ 43:21:a3:70:6c:ec:70:2b:aa:9a:14:78:8a:b9:66:58:33:c8:
+ ec:6c:1f:52:f0:c9:20:55:d4:4d:88:fe:95:fd:6f:ee:ec:26:
+ a1:98:0e:3b:93:21:85:f5:5c:24:02:2c:ee:9e:e5:ae:c9:f6:
+ 5d:3f:a0:5e:75:66:19:38:ad:0d:42:c8:49:df:20:df:b6:a3:
+ 50:eb:de:ee:36:31:17:cc:c0:2e:cf:54:d1:5a:8c:54:3d:95:
+ a1:6e:75:c2:e0:e1:fc:ba:83:b0:04:1d:be:46:3c:ee:cf:e5:
+ a1:41:37:6f:41:72:7a:44:d2:3b:e6:42:d0:01:27:14:73:84:
+ 25:e1:f5:19:8f:b2:b3:53:2b:c8:f8:9c:7a:c2:87:21:e4:e4:
+ 69:76:1c:f2:b0:fb:b1:d2:fc:50:df:61:14:45:21:a0:5c:4d:
+ 30:89:52:8c:51:1e:6a:cf:02:b1:16:b8:3b:11:24:f0:e7:46:
+ 3b:45:d6:d7:ff:ac:f8:6f:dc:8f:96:ff:a8:56:cb:e6:05:cc:
+ 2f:cc:89:7d:52:9d:00:3e:56:8d:fd:ad:77:ff:06:c0:4d:e5:
+ 0a:22:b2:51:2a:f5:e8:e1:52:d4:ac:99:f2:96:bd:88:22:85:
+ c1:ce:45:3c:c6:66:b6:18:83:72:ff:03:35:04:f4:58:9d:64:
+ 31:5e:36:b9:8f:73:e7:4d:80:d8:85:5d:34:3b:69:be:f6:89:
+ f1:e2:69:8a:4f:67:45:72:f9:66:be:1c:8f:88:98:a8:f6:8a:
+ e2:ed:50:cb:b3:64:d7:50:a4:ee:97:d7:ae:a5:88:73:bb:10:
+ 28:18:47:c2:e0:00:8e:5b:e1:14:c5:9f:8a:6e:9b:5f:29:36:
+ 36:62:35:0f:89:6d:d6:5b:19:57:8c:bf:d1:31:be:e6:a4:f3:
+ 92:86:bc:be:75:f8:8a:a3:97:27:62:ae:b6:6f:54:31:64:85:
+ 67:0b:35:7b:d3:e6:fd:2d:6b:32:7b:2d:c0:c4:25:2a:9c:26:
+ c8:31:74:8f:a0:c4:4b:4c:89:ac:72:72:8b:f3:cc:74:16:a1:
+ 93:9c:3c:47:37:7f:3a:d0:63:b0:00:b6:64:63:5c:fc:8b:52:
+ 0d:ea:2a:52:bf:b7:b1:87:f1:dd:31:e4:97:74:52:0c:0f:2e:
+ c4:87:52:db:ff:3f:45:ad:2b:65:2f:3b:cc:05:55:c2:43:c8:
+ 20:34:9d:9a:92:3f:0f:a9:05:cd:b3:cf:96:9e:51:6e:d6:06:
+ e8:4b:a8:2a:e5:44:02:60:5d:04:94:91:bf:ee:e2:62:ac:6b:
+ 75:28:13:cb:b8:f5:5e:fe:24:e3:6e:96:50:f8:0b:b8:28:a4:
+ 8c:1c:94:24:a2:4f:57:d4:93:00:f9:f5:75:a6:80:79:a3:11:
+ 00:3d:83:3e:78:72:b3:b8:eb:9a:ba:a9:0e:ae:e3:00:85:de:
+ 29:91:03:ce:33:d3:3a:a6:74:06:7d:df:4b:6f:b2:cb:b0:ff:
+ 6f:99:91:bb:82:55:3a:d3:8f:f1:e9:d5:ec:d3:15:c7:2a:8f:
+ 3d:ad:69:6a:d3:72:f8:d2:5c:dd:e6:60:c4:36:90:2b:dc:5b:
+ 40:75:f1:b6:51:14:ae:2f:f8:39:1c:e4:98:cf:86:68:a5:5f:
+ 7e:9d:12:14:56:35:29:cb:a0:59:61:d1:28:33:d3:e6:6a:58:
+ 2f:3b:5e:f7:ac:69:99:98:1a:9d:15:a5:79:aa:2d:af:75:04:
+ 0b:42:d5:2e:87:a6:6a:6a:73:a3:74:da:11:cb:28:09:5c:83:
+ 86:c7:61:be:12:4e:37:d5:80:38:9a:7c:8d:84:7c:91:8e:2c:
+ 32:89:86:ba:94:bb:64:2e:48:ec:f1:4e:c2:c3:c1:9e:13:57:
+ 72:dd:b9:a0:f5:9a:5a:b1:65:00:5e:01:25:f4:ff:30:b2:df:
+ ae:91:84:fd:53:bb:87:6c:c8:fe:3e:5d:dd:e6:ae:57:ad:5c:
+ a9:66:0b:0c:f9:c7:d4:49:fa:06:19:d3:8e:88:f6:36:43:c3:
+ 72:f2:ad:03:4c:33:17:ee:69:a5:ae:4c:1a:21:40:28:54:8a:
+ 66:ef:74:c8:43:91:e2:9c:6e:a7:b2:f2:13:fd:e0:9a:d8:9f:
+ 36:de:e6:57:06:15:60:c4:3a:d7:f3:15:d0:40:b8:2e:97:65:
+ 30:c4:f6:88:39:c4:70:d8:83:a8:6f:bd:79:a2:1d:a0:3c:11:
+ 36:54:0e:67:f8:27:42:66:9a:96:78:f2:19:8d:7d:63:05:36:
+ 4b:6b:3a:1e:31:9a:e6:ed:cb:d4:b2:76:0f:5b:da:78:8d:4a:
+ b2:90:3b:0f:85:36:c7:5b:16:00:4c:38:d8:70:89:5f:94:48:
+ 34:fb:a6:7f:3e:91:36:2b:50:9c:a8:96:9f:b0:a7:40:43:b6:
+ e1:f2:c0:6c:e8:f2:d6:8a:ad:fe:10:ad:9f:da:38:89:c8:be:
+ 2c:b5:c7:bd:77:f7:79:0f:a0:52:73:5a:ea:b8:72:98:fe:70:
+ 78:77:bf:f3:a7:5a:d9:44:85:2e:d4:7a:21:ec:07:e8:61:c1:
+ 64:9f:3d:56:5c:cb:2e:c2:f7:7d:03:70:af:3e:f2:d5:8b:b7:
+ f6:53:88:eb:8e:70:a3:b0:eb:a6:9e:00:b7:ad:70:98:e2:6e:
+ 43:2d:c2:d2:09:94:64:c3:70:9a:92:d9:7f:cc:3e:bc:c0:58:
+ af:b5:23:c1:ca:a5:55:6a:49:ff:0e:e1:69:2c:cd:6a:8d:e4:
+ fc:00:41:80:c8:ee:89:ea:07:c5:31:c2:58:31:84:f9:9f:1e:
+ ce:11:ac:a7:4e:3d:d1:ea:4a:cd:9d:e9:22:80:1d:08:74:d8:
+ 37:be:ca:15:b4:7a:bf:f0:82:8f:60:ea:e2:e9:ba:71:0e:20:
+ 94:8a:b4:15:a1:3e:85:77:b1:d1:f0:90:5b:51:ad:9f:0b:f8:
+ 8f:40:f1:e2:7a:f3:b5:d6:9b:99:ce:a9:13:6b:7b:0a:46:7d:
+ a2:27:a1:5a:a3:5f:77:2f:9a:6b:63:ef:a4:a1:70:4c:a6:2b:
+ 01:24:06:3d:0f:1b:a4:50:0c:28:0c:8f:58:90:91:96:24:2d:
+ 7f:e2:aa:73:06:94:08:e1:19:9f:9c:64:b5:82:65:22:88:07:
+ d7:bf:b6:56:3e:77:e5:c8:bd:6e:8f:72:43:06:d4:1c:81:b5:
+ f1:ec:bf:7b:f4:a8:a9:03:f7:41:56:39:82:4d:5b:2e:96:32:
+ 47:c3:be:b0:00:da:c8:86:46:05:8b:d6:20:5e:c5:39:a9:61:
+ 8a:09:40:90:3b:76:65:8a:98:8f:d4:c4:a5:cb:40:a1:e3:cc:
+ 3b:25:06:1e:be:4c:5d:ca:69:e6:b2:aa:31:9c:08:2a:b3:60:
+ db:65:90:ea:16:95:1d:14:d2:63:37:97:8c:f1:c5:1a:c4:86:
+ 1f:be:60:7c:15:17:a7:26:d9:f0:ec:dc:f8:7b:6d:c3:32:d4:
+ de:7e:08:25:2d:26:aa:30:1a:bc:b0:5d:ff:46:0c:18:fb:bf:
+ db:96:f5:ca:59:a2:76:01:4e:82:3b:63:2f:89:53:10:9d:a3:
+ b6:57:fa:39:03:63:d2:5d:ce:a1:79:80:25:b7:1a:74:9f:1d:
+ d3:95:48:a5:3c:39:d9:a7:25:ef:63:29:0e:fc:fb:a5:6a:a4:
+ 7e:61:c9:c3:90:bc:f9:23:8a:b4:68:3f:5f:cb:de:4c:34:10:
+ 81:97:cd:a7:54:67:ed:b5:47:a3:75:06:89:fc:1f:70:65:86:
+ 2d:96:98:b9:16:92:f0:c2:bd:01:a2:8e:ac:2f:d2:03:68:6b:
+ 00:d4:81:7f:dc:a1:96:7d:b5:0f:76:16:b0:1a:2f:7a:bf:7c:
+ 4e:b8:a9:35:ba:40:5e:b3:ae:6e:8a:78:85:f8:cc:64:7d:88:
+ c9:2f:5c:1f:5f:19:eb:ce:2e:60:e9:e8:77:97:5b:63:11:e9:
+ e2:fb:81:e4:01:af:54:12:8d:be:aa:29:71:2d:54:74:c0:5f:
+ 07:88:8e:3a:05:86:05:56:3f:88:3a:77:54:c8:e4:94:67:29:
+ c4:b0:47:63:0b:d7:29:97:1f:d6:5e:22:76:a1:4d:2f:76:43:
+ 20:48:7a:44:ab:d7:52:25:7f:a9:14:e6:ac:98:34:22:5c:89:
+ 6f:c8:8d:20:2a:cf:bd:c6:19:7a:3f:1c:f6:6f:08:bc:22:6e:
+ 0b:78:8f:82:1c:26:b8:82:b8:ff:ec:23:7b:13:5c:95:7d:a5:
+ 04:3f:c7:87:ed:f2:d6:fb:a2:bc:a0:ac:f2:17:c4:e4:11:56:
+ c2:2f:5c:64:66:fd:0e:c0:10:38:6c:f7:6b:ff:62:15:27:55:
+ 45:5d:fc:f0:8e:36:68:66:72:96:40:13:7f:b2:b4:4a:ca:97:
+ f2:3f:a7:ec:38:95:7a:e4:cf:da:87:b7:aa:82:57:ff:7c:d5:
+ dc:fb:e2:a8:13:9b:2a:a6:6a:7c:82:0b:be:ef:71:67:92:9e:
+ 01:83:40:1a:71:64:b3:63:57:9c:55:a6:ee:de:68:85:78:70:
+ 97:82:f5:67:74:d4:94:b1:fe:fd:f4:4f:26:ed:49:55:78:5c:
+ b2:be:da:5d:f5:18:56:8a:58:4e:82:bf:88:34:8b:9c:d4:32:
+ 49:cf:56:ae:6d:d0:45:76:9d:c7:17:7c:90:34:b9:f5:ba:a4:
+ 32:92:7c:2f:77:bc:83:df:06:22:b4:f3:eb:d2:d3:36:22:32:
+ b8:c1:4a:44:96:b2:15:13:be:44:c3:fa:55:34:fc:32:37:10:
+ f2:28:39:c9:cd:d5:e0:b2:ac:d1:38:d7:71:06:50:08:68:c3:
+ 6a:ae:86:7e:b9:a8:3e:2a:5c:b2:6f:79:64:e2:00:b5:cd:d6:
+ 18:61:da:0b:93:4c:21:f7:9d:ca:e6:e8:5c:d3:75:53:38:c3:
+ 82:e5:35:e7:62:95:4e:73:67:11:27:22:6c:0a:ac:70:89:d5:
+ c8:aa:94:b1:52:5b:b9:51:35:d8:29:33:16:9a:83:95:e2:6c:
+ 0c:ba:e6:bc:b6:bb:26:5a:e4:92:1b:21:93:0c:03:73:ae:92:
+ 0b:2b:3c:f0:6e:5e:20:88:09:f1:07:f0:8d:02:15:d5:78:4f:
+ e1:d5:8a:42:ca:e1:09:fd:34:b5:3e:a4:0d:57:54:86:40:bb:
+ 5a:0e:8c:2b:86:58:f2:e5:9f:f3:b4:0b:41:1e:ad:41:d1:8c:
+ eb:60:e2:04:1d:97:d4:4f:53:69:34:72:c7:bd:df:cb:ae:29:
+ 37:31:69:1a:d4:ba:c1:56:33:ea:e3:d5:f2:9d:45:66:5b:ad:
+ e7:59:a3:34:fb:85:de:31:41:9f:d3:ca:71:30:5b:d5:f3:46:
+ ac:d2:60:ca:a8:c7:06:35:02:fa:0c:49:91:fd:a5:7c:22:81:
+ 2e:f6:74:ad:d0:da:5f:82:3a:3d:e5:aa:05:a1:fc:aa:c6:bb:
+ 48:22:da:0b:a8:7d:96:0f:54:76:c1:7b:6c:df:2f:2a:76:29:
+ 21:f7:8a:12:08:4d:d1:f5:80:13:d2:b8:f3:7a:62:fe:f0:d8:
+ 81:df:d8:32:ea:45:31:ac:6b:b7:58:f7:29:aa:1f:43:73:23:
+ ea:be:e2:cb:56:32:17:23:fa:25:8b:98:51:b4:b1:43:0d:0b:
+ 9c:5e:e7:d2:7c:1b:e0:4c:c5:df:88:fd:b7:2c:4a:19:3f:38:
+ 4d:6c:f0:ab:97:42:65:df:47:31:8e:76:4b:7a:9a:0f:65:67:
+ 07:c7:fe:84:af:de:e9:33:d3:97:4a:9f:78:74:aa:1f:99:8e:
+ 5e:15:59:f4:60:e5:3a:56:25:7b:52:b5:3e:ec:f2:1f:6b:3c:
+ 89:dc:58:01:9a:ec:dd:f3:21:c6:4e:57:e0:b6:80:1a:03:2a:
+ 9f:13:a9:92:d3:3b:b1:6b:db:1c:9e:7b:79:74:59:64:eb:8a:
+ 99:16:2f:bf:78:c1:de:a8:61:46:3d:93:0f:12:dc:0f:a6:d9:
+ ff:ae:b1:c2:2a:b3:aa:a3:6c:c1:fa:b0:a8:79:73:07:a4:2e:
+ 6f:ac:34:f4:99:c3:03:86:21:2c:17:e0:a3:b2:76:bd:32:31:
+ 9b:9a:98:35:d5:63:08:06:f1:3b:4e:d0:db:25:87:5f:75:14:
+ 33:ba:70:a3:a8:64:30:8b:3e:d0:cc:56:61:c6:ab:cc:8e:db:
+ ff:a9:38:bf:92:8c:30:08:bf:84:71:61:18:df:15:7a:01:cf:
+ d5:53:6d:f4:6c:64:5f:82:7e:14:b6:77:17:fc:d5:6c:44:02:
+ bf:41:db:ae:e6:d8:3e:29:50:c1:d7:be:63:cc:9c:27:26:49:
+ 8f:90:9b:cc:f4:c1:c4:82:8f:54:18:6e:f4:9f:d9:8f:03:5f:
+ 15:77:d7:fe:d0:a7:f7:21:ce:31:1a:05:9f:95:53:2e:cf:bf:
+ ec:f2:bc:81:8d:01:a9:47:74:71:0d:23:dc:28:45:93:b7:5d:
+ b7:98:7c:ef:25:e1:2b:25:9e:fe:47:34:29:e7:48:db:94:22:
+ bb:c0:d5:2a:03:74:4d:12:95:41:d4:dc:c1:97:af:f5:8c:e4:
+ e1:ff:39:a1:5b:3f:33:df:22:dd:ff:67:17:92:a3:f4:c1:af:
+ 6a:d9:27:17:ff:88:d0:3a:ee:d8:e9:32:b6:83:8c:46:6f:13:
+ 52:98:f3:66:90:be:e8:f3:0d:24:9c:7d:cf:e4:60:3c:eb:b3:
+ 78:70:a8:57:c5:22:fe:6b:1e:d2:31:ba:46:60:d2:ae:29:9c:
+ 47:fd:1b:28:89:aa:f9:af:f8:ca:c5:2e:e2:66:67:fc:75:2d:
+ 9f:6c:a2:48:d4:ea:93:2b:2a:7e:a5:11:31:64:d9:57:0c:75:
+ 79:cb:0d:2e:bf:0e:69:36:51:8e:46:7e:56:df:ad:56:73:09:
+ f8:78:29:f0:63:b2:a6:c3:b6:f2:83:54:c6:fa:75:f5:24:ef:
+ 5a:ba:bd:03:b2:ea:3c:d0:d8:cc:d2:45:00:be:75:38:ba:46:
+ af:09:a3:67:52:ea:46:d7:c7:01:74:cd:2f:48:df:f1:1f:64:
+ 2e:af:e5:99:76:b3:c3:3e:47:c8:be:2c:8a:1f:ec:4c:ea:16:
+ f8:09:b1:78:32:90:dd:75:bf:db:ce:ce:d9:96:7e:85:ac:c6:
+ a5:b9:c9:48:df:11:d3:eb:05:59:07:2c:a4:ac:06:d8:6f:b3:
+ b0:09:9b:0b:c4:ca:65:88:89:1e:76:fa:12:bb:66:cc:f8:7f:
+ d1:90:42:6c:f9:b9:5f:2d:9a:62:00:96:2e:ef:b2:5c:16:52:
+ d7:2e:b0:e6:52:f0:b6:d5:c2:d5:e9:73:c4:a1:42:c5:55:34:
+ 55:3a:75:c5:61:09:ac:a9:2d:70:d3:c7:c7:48:c8:8b:0c:2e:
+ 76:fb:7c:1b:e2:2a:1e:0e:d8:6a:95:b0:2c:2c:8f:43:13:56:
+ 68:d6:a0:85:66:9d:48:75:e0:46:89:3b:b0:b1:5d:3f:8e:a8:
+ d2:db:ae:35:2b:8d:b3:11:77:aa:c6:a5:11:e8:3a:d4:6b:ba:
+ 45:bf:44:e7:9e:63:6d:6c:11:58:c1:7a:74:d2:7d:85:78:ed:
+ ee:9e:dd:c5:07:b1:f5:c9:9e:d0:d6:d6:9e:2f:89:7f:67:47:
+ e1:51:79:72:f1:2a:5a:9f:7d:c6:8a:a3:9a:87:26:00:0a:06:
+ 2a:0a:95:f0:5f:6e:81:e1:71:cf:af:b3:3f:93:1b:23:47:d3:
+ 28:8d:8c:f6:c1:cc:12:03:c3:4f:dd:d5:e5:a9:87:2a:1f:c0:
+ ff:0a:69:7e:73:55:d7:87:79:9f:7d:fd:f4:f1:4b:79:49:d9:
+ 53:8c:58:22:e0:4f:21:bf:d6:56:cb:bd:b3:aa:a8:b4:d2:93:
+ 60:ac:f3:cd:57:80:27:11:ee:98:e8:c2:93:12:b6:a7:0d:a1:
+ 5c:e0:64:2d:8d:d7:09:70:ac:b3:c4:21:44:76:04:5d:f5:cb:
+ ec:15:ba:b6:1c:66:c5:72:3d:07:2e:b7:26:ae:26:8c:c0:01:
+ 46:7c:c7:85:63:35:6c:8d:47:bf:22:60:b3:40:e9:fa:05:06:
+ 7d:61:26:ed:ed:f2:b0:a2:fd:d0:e5:1b:ab:0a:8e:01:df:93:
+ 50:89:76:b6:ee:49:41:24:e6:41:cd:10:b1:48:73:f3:63:7c:
+ 72:41:83:23:89:cb:dc:af:f7:9c:7f:9d:55:68:f7:d8:c7:a6:
+ 57:75:38:0b:21:b6:37:b0:ec:ec:05:e6:f9:03:b7:b2:c4:fe:
+ d3:d2:29:29:a9:5d:54:ce:d4:72:4f:51:b7:a0:f4:45:95:f3:
+ 7a:2f:33:a4:3e:b0:34:f5:ff:68:1c:4a:6a:da:62:cd:1d:fe:
+ a7:ce:5a:b5:eb:79:08:cd:c2:78:d3:78:bf:04:af:7f:12:a2:
+ 5b:af:c0:b2:67:be:44:64:b6:d7:13:86:e2:bf:19:c6:e4:f6:
+ e0:4f:21:1f:68:1d:77:3f:6f:08:fa:54:cc:a7:b5:6a:6f:fd:
+ a7:9d:61:37:5a:94:46:af:71:2a:bc:8a:5e:44:d7:71:cb:49:
+ c2:fd:51:ff:d8:ab:35:23:1a:84:47:4e:a0:af:e5:55:ce:bd:
+ 49:76:57:35:4f:8b:90:23:a4:d0:11:a8:c9:bd:7e:7f:e5:d2:
+ c9:c0:4f:b0:95:13:25:ae:af:54:77:b1:e0:12:34:59:25:d1:
+ 8d:8f:36:2f:2a:ac:30:38:3e:4e:00:36:43:76:12:88:f3:d4:
+ c1:fa:73:e5:84:27:c0:57:94:4c:34:03:55:cc:ff:6e:a3:c2:
+ c5:d3:2f:2d:ac:7c:2b:74:3a:1f:40:74:df:92:60:d4:64:b4:
+ bb:6c:26:3e:88:6d:1d:96:e3:c6:44:01:5a:61:1f:9f:81:99:
+ 32:77:2e:ee:a6:85:9c:99:a5:b9:0d:e3:31:98:a9:38:bc:ee:
+ cf:65:07:85:5e:77:de:b7:b4:f8:91:1d:ca:45:47:00:b8:80:
+ ef:66:20:cb:f1:10:0f:d8:95:22:c1:fd:c1:98:29:1e:a6:44:
+ d6:2b:73:d9:73:31:fd:9d:fa:f2:b1:1b:eb:7e:3a:a3:fa:c4:
+ 8b:d6:49:54:c9:2b:1c:e7:14:00:ec:30:34:4f:c0:cf:10:5a:
+ 18:27:fa:c6:ff:21:11:6b:55:ff:d6:84:0b:e4:c7:0f:60:5b:
+ 59:d8:84:72:59:9f:e7:8b:70:8f:3f:5c:88:19:3e:b5:27:b7:
+ 45:ea:65:6d:7d:7c:86:55:7e:e1:a9:f3:5a:f4:3f:11:cd:ce:
+ 90:c8:f2:d9:a6:2d:04:ed:f1:89:40:1f:7f:4e:a4:e6:d6:b9:
+ af:e5:40:e9:b6:88:38:17:e3:49:98:b6:9e:ae:84:7e:5a:fa:
+ 03:85:7f:a1:9f:b3:1a:39:ee:7c:7f:e1:22:e3:9f:ec:f7:94:
+ 15:78:a1:a8:a1:10:8f:29:73:28:a9:c0:df:21:2f:a0:56:5e:
+ ea:fc:9f:7b:c2:96:4c:78:56:79:b3:be:de:54:80:04:a0:af:
+ 7f:f4:63:52:eb:30:ba:db:79:a3:9d:36:12:3a:79:03:76:6d:
+ ea:65:d2:b8:b3:ab:75:b6:0a:ee:09:be:fd:23:e9:32:76:4d:
+ 00:78:35:a1:b4:e5:4b:d6:a3:34:17:16:fd:9e:9d:d5:75:8e:
+ 3b:b1:84:8b:4b:8a:fc:e6:9a:4a:79:64:b0:0c:4d:5c:f0:b2:
+ 8c:b0:2c:d3:74:db:18:2d:18:7b:83:57:7c:fc:1d:09:9e:bb:
+ 0e:df:42:d0:8b:4e:5e:7f:ee:9f:bf:2c:d9:39:23:7b:e1:a1:
+ 47:b5:6f:c4:9f:62:85:e7
+-----BEGIN CERTIFICATE-----
+MIIg8TCCAiugAwIBAgIUGrmlEei1Lm0G28g53xpQBCEe+UQwCwYJYIZIAWUDBAMa
+MIGjMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UEBwwHQm96
+ZW1hbjEYMBYGA1UECgwPd29sZlNTTF9TTEgtRFNBMRswGQYDVQQLDBJSb290LVNM
+SC1EU0Etc2hha2UxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3
+DQEJARYQaW5mb0B3b2xmc3NsLmNvbTAeFw0yNjA0MjgwODEwMDRaFw0yOTAxMjIw
+ODEwMDRaMIGjMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9udGFuYTEQMA4GA1UE
+BwwHQm96ZW1hbjEYMBYGA1UECgwPd29sZlNTTF9TTEgtRFNBMRswGQYDVQQLDBJS
+b290LVNMSC1EU0Etc2hha2UxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEfMB0G
+CSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTAwMAsGCWCGSAFlAwQDGgMhACqM
+jF2YEIFMAUeaq+RxTydF4qwQjpWAlJQwoaItuSqzo2MwYTAdBgNVHQ4EFgQUYpCQ
+5Tp0GBv7aEAHpYOdcC5+yfAwHwYDVR0jBBgwFoAUYpCQ5Tp0GBv7aEAHpYOdcC5+
+yfAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwCwYJYIZIAWUDBAMa
+A4IesQDzACESZkMN+yy+Er7tB0+ueBtMz+LohcvkaPBubmN44RAMPH0PVwPcrHOB
+fMled+LQdwqxfgPMO/akGeTh3YnV3c8Lwheg/f0I2NPIEhyo3rugp7qBu8mwvQn9
+Bc/4iVw41w+mj4WYzT6jwZrpTGu7DiX8PYMZU4o4/+gb2ss1JD4c3oydg/cqnlJL
+GwoU5rQfW94NP2iMq+na2GE3wsDjKLLfQQ7SkDDi9qQYoR9DxDCjaJ3B1oHRKi6m
+an3Ho2tP8W5nXbAIS58PwZhPXhOARNfA1zLWr0Khy8CG89PbF4nt8b2v8+t28Sqc
+DNnJhotG+ArPX+f7UKFp5V6leIFjWpwtpLIr1vrB+X56ATmxDxey5nSwAVlZFPZJ
+a/ljSoQyg4Asz45OC/b/QUdrwvdUz0QJDhLNjo/wWASAIKhw26aojLo9lwbSDz3P
+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
+-----END CERTIFICATE-----
diff --git a/examples/certs/update-certs.sh b/examples/certs/update-certs.sh
index 4aca7b9f..ef899bcb 100755
--- a/examples/certs/update-certs.sh
+++ b/examples/certs/update-certs.sh
@@ -102,6 +102,16 @@ lmsCertList=(
"lms/bc_lms_native_bc_root.der"
)
+# SLH-DSA (FIPS 205) self-signed root certs + private keys, used by the
+# wolfJCE WKS KeyStore tests. Copied from native wolfSSL certs/slhdsa/ when
+# present.
+slhdsaCertList=(
+ "slhdsa/root-slhdsa-sha2-128s.pem"
+ "slhdsa/root-slhdsa-sha2-128s-priv.pem"
+ "slhdsa/root-slhdsa-shake-128s.pem"
+ "slhdsa/root-slhdsa-shake-128s-priv.pem"
+)
+
for i in ${!certList[@]};
do
printf "Updating: ${certList[$i]}\n"
@@ -160,6 +170,22 @@ else
printf "local copies\n"
fi
+if [ -d "$CERT_LOCATION/slhdsa" ]; then
+ for i in ${!slhdsaCertList[@]};
+ do
+ printf "Updating: ${slhdsaCertList[$i]}\n"
+ cp $CERT_LOCATION/${slhdsaCertList[$i]} ./${slhdsaCertList[$i]}
+ if [ $? -ne 0 ]; then
+ printf "Warning: skipped missing SLH-DSA cert: "
+ printf "${slhdsaCertList[$i]}\n"
+ fi
+ done
+else
+ printf "Notice: $CERT_LOCATION/slhdsa not found, skipping SLH-DSA\n"
+ printf "certs (provided wolfSSL predates them), keeping existing\n"
+ printf "local copies\n"
+fi
+
# Generate ca-keyPkcs8.der, used by examples/X509CertificateGeneration.java
openssl pkcs8 -topk8 -inform DER -outform DER -in ca-key.der -out ca-keyPkcs8.der -nocrypt
if [ $? -ne 0 ]; then
diff --git a/examples/provider/SlhDsaExample.java b/examples/provider/SlhDsaExample.java
new file mode 100644
index 00000000..b4a7464c
--- /dev/null
+++ b/examples/provider/SlhDsaExample.java
@@ -0,0 +1,231 @@
+/* SlhDsaExample.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import com.wolfssl.provider.jce.WolfCryptProvider;
+import com.wolfssl.provider.jce.WolfCryptContextParameterSpec;
+
+/**
+ * Example demonstrating SLH-DSA (FIPS 205) sign and verify using wolfJCE.
+ *
+ * For each demonstrated SLH-DSA parameter set this example:
+ *
+ * 1. Generates a key pair with KeyPairGenerator.
+ * 2. Signs a message with Signature (initSign / update / sign).
+ * 3. Verifies the signature with Signature (initVerify / update / verify).
+ * 4. Confirms a tampered message fails verification.
+ * 5. Encodes the public key to X.509 SubjectPublicKeyInfo DER and the
+ * private key to PKCS#8 DER, decodes both back through KeyFactory, and
+ * verifies the round-tripped keys still work.
+ *
+ * It then demonstrates the optional FIPS 205 context string via
+ * WolfCryptContextParameterSpec.
+ *
+ * To keep the run quick this example uses the "fast" (f) parameter sets,
+ * which have faster signing than the "small" (s) sets. Any of the 12 FIPS 205
+ * parameter sets may be substituted. Native wolfSSL must be built with
+ * --enable-slhdsa. If SLH-DSA is not compiled into native wolfCrypt, this
+ * example prints a notice and exits cleanly.
+ */
+public class SlhDsaExample {
+
+ /* A representative selection of FIPS 205 parameter sets (fast variants
+ * across the SHA2 and SHAKE families and all three security levels). */
+ private static final String[] PARAM_SETS = {
+ "SLH-DSA-SHA2-128f",
+ "SLH-DSA-SHA2-192f",
+ "SLH-DSA-SHAKE-128f",
+ "SLH-DSA-SHAKE-256f"
+ };
+
+ public static void main(String[] args) throws Exception {
+
+ /* Install wolfJCE as the highest-priority provider at runtime. */
+ Security.insertProviderAt(new WolfCryptProvider(), 1);
+
+ /* Detect whether SLH-DSA is available in this wolfJCE build. */
+ try {
+ KeyPairGenerator.getInstance("SLH-DSA", "wolfJCE");
+ } catch (Exception e) {
+ System.out.println(
+ "SLH-DSA key gen not available in this wolfJCE build.");
+ System.out.println(
+ "Rebuild native wolfSSL with SLH-DSA support " +
+ "(--enable-slhdsa). Note that a verify-only native build " +
+ "(--enable-slhdsa=yes,verify-only) registers only the " +
+ "Signature and KeyFactory services, not KeyPairGenerator.");
+ return;
+ }
+
+ byte[] msg = "Everyone gets Friday off.".getBytes();
+
+ System.out.println("wolfJCE SLH-DSA (FIPS 205) Example");
+ System.out.println("=================================");
+
+ for (String set : PARAM_SETS) {
+ runParamSet(set, msg);
+ }
+
+ contextStringDemo(msg);
+
+ System.out.println("\nAll SLH-DSA examples completed successfully.");
+ }
+
+ /**
+ * Run the full sign/verify and key-encoding demonstration for a single
+ * SLH-DSA parameter set. A parameter set not compiled into the native
+ * build is skipped with a notice.
+ *
+ * @param set SLH-DSA parameter-set name, e.g. "SLH-DSA-SHA2-128f"
+ * @param msg message bytes to sign and verify
+ */
+ private static void runParamSet(String set, byte[] msg) throws Exception {
+
+ System.out.println("\n[" + set + "]");
+
+ KeyPair kp;
+ try {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance(set, "wolfJCE");
+ kp = kpg.generateKeyPair();
+ } catch (Exception e) {
+ System.out.println(" skipping, key generation failed (" +
+ e.getMessage() + ")");
+ return;
+ }
+ System.out.println(" generated key pair");
+
+ /* 2. Sign the message. The generic "SLH-DSA" Signature accepts a key
+ * of any parameter set, the per-set name works too. */
+ Signature signer = Signature.getInstance("SLH-DSA", "wolfJCE");
+ signer.initSign(kp.getPrivate());
+ signer.update(msg);
+ byte[] sig = signer.sign();
+ System.out.println(" signed message, signature is " +
+ sig.length + " bytes");
+
+ /* 3. Verify the signature. */
+ Signature verifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ verifier.initVerify(kp.getPublic());
+ verifier.update(msg);
+ if (!verifier.verify(sig)) {
+ throw new Exception(set + ": signature did not verify");
+ }
+ System.out.println(" signature verified");
+
+ /* 4. A tampered message must not verify. */
+ byte[] tampered = msg.clone();
+ tampered[0] ^= (byte)0x01;
+ Signature badVerifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ badVerifier.initVerify(kp.getPublic());
+ badVerifier.update(tampered);
+ if (badVerifier.verify(sig)) {
+ throw new Exception(set + ": tampered message verified");
+ }
+ System.out.println(" tampered message correctly rejected");
+
+ /* 5. Encode keys to DER, decode through KeyFactory, and confirm the
+ * round-tripped keys still verify. */
+ byte[] pubDer = kp.getPublic().getEncoded();
+ byte[] privDer = kp.getPrivate().getEncoded();
+ System.out.println(" public key X.509 DER is " +
+ pubDer.length + " bytes, private key PKCS#8 DER is " +
+ privDer.length + " bytes");
+
+ KeyFactory kf = KeyFactory.getInstance(set, "wolfJCE");
+ PublicKey decodedPub =
+ kf.generatePublic(new X509EncodedKeySpec(pubDer));
+ PrivateKey decodedPriv =
+ kf.generatePrivate(new PKCS8EncodedKeySpec(privDer));
+
+ Signature reSigner = Signature.getInstance("SLH-DSA", "wolfJCE");
+ reSigner.initSign(decodedPriv);
+ reSigner.update(msg);
+ byte[] sig2 = reSigner.sign();
+
+ Signature reVerifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ reVerifier.initVerify(decodedPub);
+ reVerifier.update(msg);
+ if (!reVerifier.verify(sig2)) {
+ throw new Exception(
+ set + ": re-encoded key signature did not verify");
+ }
+ System.out.println(" key encode/decode round trip verified");
+ }
+
+ /**
+ * Demonstrate the optional FIPS 205 context string. A signature made with
+ * a given context only verifies when the same context is supplied.
+ *
+ * @param msg message bytes to sign and verify
+ */
+ private static void contextStringDemo(byte[] msg) throws Exception {
+
+ String set = "SLH-DSA-SHA2-128f";
+
+ System.out.println("\n[context string demo, " + set + "]");
+
+ KeyPair kp;
+ try {
+ kp = KeyPairGenerator.getInstance(set, "wolfJCE")
+ .generateKeyPair();
+ } catch (Exception e) {
+ System.out.println(" skipping, key generation failed (" +
+ e.getMessage() + ")");
+ return;
+ }
+ byte[] ctx = "my-application-v1".getBytes();
+
+ Signature signer = Signature.getInstance("SLH-DSA", "wolfJCE");
+ signer.setParameter(new WolfCryptContextParameterSpec(ctx));
+ signer.initSign(kp.getPrivate());
+ signer.update(msg);
+ byte[] sig = signer.sign();
+ System.out.println(" signed with context \"my-application-v1\"");
+
+ /* Same context verifies. */
+ Signature okVerifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ okVerifier.setParameter(new WolfCryptContextParameterSpec(ctx));
+ okVerifier.initVerify(kp.getPublic());
+ okVerifier.update(msg);
+ if (!okVerifier.verify(sig)) {
+ throw new Exception("context: same-context verify failed");
+ }
+ System.out.println(" same context verified");
+
+ /* Empty (default) context must not verify. */
+ Signature emptyVerifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ emptyVerifier.initVerify(kp.getPublic());
+ emptyVerifier.update(msg);
+ if (emptyVerifier.verify(sig)) {
+ throw new Exception("context: empty-context verify succeeded");
+ }
+ System.out.println(" empty context correctly rejected");
+ }
+}
diff --git a/examples/provider/SlhDsaExample.sh b/examples/provider/SlhDsaExample.sh
new file mode 100755
index 00000000..ffdda82b
--- /dev/null
+++ b/examples/provider/SlhDsaExample.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+cd ./examples/build/provider
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../../lib/:/usr/local/lib
+java -classpath ../../../lib/wolfcrypt-jni.jar:./ -Dsun.boot.library.path=../../../lib/ SlhDsaExample "$@"
+
diff --git a/jni/include/com_wolfssl_wolfcrypt_Asn.h b/jni/include/com_wolfssl_wolfcrypt_Asn.h
index 8a6b8565..3ce211f3 100644
--- a/jni/include/com_wolfssl_wolfcrypt_Asn.h
+++ b/jni/include/com_wolfssl_wolfcrypt_Asn.h
@@ -73,6 +73,102 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getML_1DSA_1LEVEL3k
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getML_1DSA_1LEVEL5k
(JNIEnv *, jclass);
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHA2_128Sk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1128Sk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHA2_128Fk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1128Fk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHA2_192Sk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1192Sk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHA2_192Fk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1192Fk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHA2_256Sk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1256Sk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHA2_256Fk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1256Fk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHAKE_128Sk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1128Sk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHAKE_128Fk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1128Fk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHAKE_192Sk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1192Sk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHAKE_192Fk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1192Fk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHAKE_256Sk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1256Sk
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getSLH_DSA_SHAKE_256Fk
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1256Fk
+ (JNIEnv *, jclass);
+
/*
* Class: com_wolfssl_wolfcrypt_Asn
* Method: getMD5h
diff --git a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h
index b29abdcf..c196d87d 100644
--- a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h
+++ b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h
@@ -423,6 +423,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_MlDsaEnabled
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_SlhDsaEnabled
(JNIEnv *, jclass);
+/*
+ * Class: com_wolfssl_wolfcrypt_FeatureDetect
+ * Method: SlhDsaKeyGenEnabled
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_SlhDsaKeyGenEnabled
+ (JNIEnv *, jclass);
+
/*
* Class: com_wolfssl_wolfcrypt_FeatureDetect
* Method: LmsEnabled
diff --git a/jni/include/com_wolfssl_wolfcrypt_SlhDsa.h b/jni/include/com_wolfssl_wolfcrypt_SlhDsa.h
new file mode 100644
index 00000000..b659b08e
--- /dev/null
+++ b/jni/include/com_wolfssl_wolfcrypt_SlhDsa.h
@@ -0,0 +1,247 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_wolfssl_wolfcrypt_SlhDsa */
+
+#ifndef _Included_com_wolfssl_wolfcrypt_SlhDsa
+#define _Included_com_wolfssl_wolfcrypt_SlhDsa
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_wolfssl_wolfcrypt_SlhDsa_NULL
+#define com_wolfssl_wolfcrypt_SlhDsa_NULL 0LL
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_128S
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_128S 0L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_128F
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_128F 1L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_192S
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_192S 2L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_192F
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_192F 3L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_256S
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_256S 4L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_256F
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHAKE_256F 5L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_128S
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_128S 6L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_128F
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_128F 7L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_192S
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_192S 8L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_192F
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_192F 9L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_256S
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_256S 10L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_256F
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_SHA2_256F 11L
+#undef com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN
+#define com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN 255L
+#undef com_wolfssl_wolfcrypt_SlhDsa_PARAM_MIN
+#define com_wolfssl_wolfcrypt_SlhDsa_PARAM_MIN 0L
+#undef com_wolfssl_wolfcrypt_SlhDsa_PARAM_MAX
+#define com_wolfssl_wolfcrypt_SlhDsa_PARAM_MAX 11L
+#undef com_wolfssl_wolfcrypt_SlhDsa_PARAM_UNSET
+#define com_wolfssl_wolfcrypt_SlhDsa_PARAM_UNSET -1L
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: mallocNativeStruct
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_mallocNativeStruct
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_init
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1init
+ (JNIEnv *, jobject, jint);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_free
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1free
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_get_param
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1get_1param
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_make_key
+ * Signature: (Lcom/wolfssl/wolfcrypt/Rng;)V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1make_1key
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_make_key_with_seeds
+ * Signature: ([B[B[B)V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1make_1key_1with_1seeds
+ (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_sign
+ * Signature: ([B[BLcom/wolfssl/wolfcrypt/Rng;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign
+ (JNIEnv *, jobject, jbyteArray, jbyteArray, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_sign_deterministic
+ * Signature: ([B[B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign_1deterministic
+ (JNIEnv *, jobject, jbyteArray, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_sign_hash
+ * Signature: ([BI[BLcom/wolfssl/wolfcrypt/Rng;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign_1hash
+ (JNIEnv *, jobject, jbyteArray, jint, jbyteArray, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_sign_msg_prehash
+ * Signature: ([B[BLcom/wolfssl/wolfcrypt/Rng;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign_1msg_1prehash
+ (JNIEnv *, jobject, jbyteArray, jbyteArray, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_verify
+ * Signature: ([B[B[B)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1verify
+ (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_verify_hash
+ * Signature: ([B[BI[B)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1verify_1hash
+ (JNIEnv *, jobject, jbyteArray, jbyteArray, jint, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_verify_msg_prehash
+ * Signature: ([B[B[B)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1verify_1msg_1prehash
+ (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_export_public
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1export_1public
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_export_private
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1export_1private
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_import_public
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1import_1public
+ (JNIEnv *, jobject, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_import_private
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1import_1private
+ (JNIEnv *, jobject, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_PublicKeyToDer
+ * Signature: (Z)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1PublicKeyToDer
+ (JNIEnv *, jobject, jboolean);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_KeyToDer
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1KeyToDer
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_PublicKeyDecode
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1PublicKeyDecode
+ (JNIEnv *, jobject, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_PrivateKeyDecode
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1PrivateKeyDecode
+ (JNIEnv *, jobject, jbyteArray);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_pub_size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1pub_1size
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_priv_size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1priv_1size
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_sig_size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sig_1size
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_wolfssl_wolfcrypt_SlhDsa
+ * Method: wc_SlhDsaKey_check_key
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1check_1key
+ (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/jni/jni_asn.c b/jni/jni_asn.c
index 21550103..f7c53de9 100644
--- a/jni/jni_asn.c
+++ b/jni/jni_asn.c
@@ -134,6 +134,153 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getML_1DSA_1LEVEL5k
#endif
}
+/* SLH-DSA (FIPS 205) Key_Sum enum values from oid_sum.h, present when native
+ * wolfSSL is built with SLH-DSA support. Return 0 (treated as unsupported by
+ * Java callers) when SLH-DSA is not compiled in. */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1128Sk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHA2_128Sk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1128Fk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHA2_128Fk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1192Sk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHA2_192Sk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1192Fk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHA2_192Fk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1256Sk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHA2_256Sk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHA2_1256Fk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHA2_256Fk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1128Sk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHAKE_128Sk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1128Fk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHAKE_128Fk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1192Sk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHAKE_192Sk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1192Fk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHAKE_192Fk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1256Sk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHAKE_256Sk;
+#else
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getSLH_1DSA_1SHAKE_1256Fk
+ (JNIEnv* env, jclass class)
+{
+ (void)env;
+ (void)class;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ return (jint)SLH_DSA_SHAKE_256Fk;
+#else
+ return 0;
+#endif
+}
+
JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Asn_encodeSignature__Ljava_nio_ByteBuffer_2Ljava_nio_ByteBuffer_2JI(
JNIEnv* env, jclass class, jobject encoded_object, jobject hash_object,
jlong hashSize, jint hashOID)
diff --git a/jni/jni_feature_detect.c b/jni/jni_feature_detect.c
index dde1ade4..7dc823e4 100644
--- a/jni/jni_feature_detect.c
+++ b/jni/jni_feature_detect.c
@@ -655,6 +655,18 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_SlhDsaEnable
#endif
}
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_SlhDsaKeyGenEnabled
+ (JNIEnv* env, jclass jcl)
+{
+ (void)env;
+ (void)jcl;
+#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY)
+ return JNI_TRUE;
+#else
+ return JNI_FALSE;
+#endif
+}
+
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_LmsEnabled
(JNIEnv* env, jclass jcl)
{
diff --git a/jni/jni_slhdsa.c b/jni/jni_slhdsa.c
new file mode 100644
index 00000000..58470aed
--- /dev/null
+++ b/jni/jni_slhdsa.c
@@ -0,0 +1,1687 @@
+/* jni_slhdsa.c
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#include
+
+#ifdef WOLFSSL_USER_SETTINGS
+ #include
+#elif !defined(__ANDROID__)
+ #include
+#endif
+
+#include
+#include
+
+#ifdef WOLFSSL_HAVE_SLHDSA
+ #include
+ #include
+ #include
+ #include
+ #include
+#endif
+#include
+#include
+
+#include
+#include
+#include
+
+/* #define WOLFCRYPT_JNI_DEBUG_ON */
+#include
+
+/* A WOLFSSL_SLHDSA_VERIFY_ONLY build provides only public-key verify. DER
+ * encode (KeyToDer / PublicKeyToDer) additionally needs
+ * WC_ENABLE_ASYM_KEY_EXPORT. */
+#ifdef WOLFSSL_HAVE_SLHDSA
+
+#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY
+ #define WC_JNI_SLHDSA_HAVE_MAKE_KEY
+ #define WC_JNI_SLHDSA_HAVE_SIGN
+ #define WC_JNI_SLHDSA_HAVE_PRIV_KEY
+#endif
+#ifdef WC_ENABLE_ASYM_KEY_EXPORT
+ #define WC_JNI_SLHDSA_HAVE_ASN1_EXPORT
+#endif
+
+#endif /* WOLFSSL_HAVE_SLHDSA */
+
+#ifdef WOLFSSL_HAVE_SLHDSA
+/* FIPS 205 Section 10.2.2 HashSLH-DSA pre-hash digest lengths for the SHAKE
+ * parameter sets: SHAKE128 produces a 256-bit (32-byte) output and SHAKE256 a
+ * 512-bit (64-byte) output. wolfSSL has no fixed-size constant for these XOFs,
+ * so name them explicitly here rather than borrowing the SHA-2 sizes. */
+#define WC_JNI_SLHDSA_SHAKE128_PH_LEN 32
+#define WC_JNI_SLHDSA_SHAKE256_PH_LEN 64
+
+/* Maximum FIPS 205 pre-hash digest length across the PH functions
+ * (SHA-256 = 32, SHA-512 = 64, SHAKE128 = 32, SHAKE256 = 64), used to size
+ * local digest buffers. */
+#ifdef WOLFSSL_SHA512
+ #define WC_JNI_SLHDSA_MAX_PH_LEN WC_SHA512_DIGEST_SIZE
+#else
+ #define WC_JNI_SLHDSA_MAX_PH_LEN WC_JNI_SLHDSA_SHAKE256_PH_LEN
+#endif
+
+/* Compute the FIPS 205 Section 10.2.2 HashSLH-DSA pre-hash PH(msg) into the
+ * caller's 64-byte digest buffer, selecting the hash function and digest
+ * length from the key's parameter set per the standardized pre-hash OIDs:
+ * SHA2-128 -> SHA-256 (32 bytes)
+ * SHA2-192/256 -> SHA-512 (64 bytes)
+ * SHAKE-128 -> SHAKE128 (32 bytes)
+ * SHAKE-192/256 -> SHAKE256 (64 bytes)
+ * On success returns 0 and sets *digestLen and *hashType. */
+static int slhdsa_prehash_msg(SlhDsaKey* key, const byte* msg, word32 msgLen,
+ byte* digest, word32* digestLen, enum wc_HashType* hashType)
+{
+ int ret;
+ int param;
+ int category;
+
+ if (key == NULL || key->params == NULL || digest == NULL ||
+ digestLen == NULL || hashType == NULL) {
+ return BAD_FUNC_ARG;
+ }
+
+ param = (int)key->params->param;
+ /* Category index: 0 = 128-bit, 1 = 192-bit, 2 = 256-bit. */
+ category = (param % 6) / 2;
+
+ if (SLHDSA_IS_SHA2(param)) {
+ if (category == 0) {
+#ifndef NO_SHA256
+ ret = wc_Sha256Hash(msg, msgLen, digest);
+ *digestLen = WC_SHA256_DIGEST_SIZE;
+ *hashType = WC_HASH_TYPE_SHA256;
+#else
+ ret = NOT_COMPILED_IN;
+#endif
+ }
+ else {
+#ifdef WOLFSSL_SHA512
+ ret = wc_Sha512Hash(msg, msgLen, digest);
+ *digestLen = WC_SHA512_DIGEST_SIZE;
+ *hashType = WC_HASH_TYPE_SHA512;
+#else
+ ret = NOT_COMPILED_IN;
+#endif
+ }
+ }
+ else {
+ if (category == 0) {
+#ifdef WOLFSSL_SHAKE128
+ ret = wc_Shake128Hash(msg, msgLen, digest,
+ WC_JNI_SLHDSA_SHAKE128_PH_LEN);
+ *digestLen = WC_JNI_SLHDSA_SHAKE128_PH_LEN;
+ *hashType = WC_HASH_TYPE_SHAKE128;
+#else
+ ret = NOT_COMPILED_IN;
+#endif
+ }
+ else {
+#ifdef WOLFSSL_SHAKE256
+ ret = wc_Shake256Hash(msg, msgLen, digest,
+ WC_JNI_SLHDSA_SHAKE256_PH_LEN);
+ *digestLen = WC_JNI_SLHDSA_SHAKE256_PH_LEN;
+ *hashType = WC_HASH_TYPE_SHAKE256;
+#else
+ ret = NOT_COMPILED_IN;
+#endif
+ }
+ }
+
+ return ret;
+}
+#endif /* WOLFSSL_HAVE_SLHDSA */
+
+JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_mallocNativeStruct
+ (JNIEnv* env, jobject this)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ SlhDsaKey* key = NULL;
+
+ key = (SlhDsaKey*)XMALLOC(sizeof(SlhDsaKey), NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (key == NULL) {
+ throwOutOfMemoryException(env, "Failed to allocate SlhDsa object");
+ }
+ else {
+ XMEMSET(key, 0, sizeof(SlhDsaKey));
+ }
+
+ LogStr("new SlhDsa() = %p\n", key);
+
+ return (jlong)(uintptr_t)key;
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+ return (jlong)0;
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1init
+ (JNIEnv* env, jobject this, jint param)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ if (key == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_Init(key, (enum SlhDsaParam)param, NULL,
+ INVALID_DEVID);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_Init(key=%p, param=%d) = %d\n", key, (int)param, ret);
+#else
+ (void)env;
+ (void)this;
+ (void)param;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1free
+ (JNIEnv* env, jobject this)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ SlhDsaKey* key = NULL;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ if (key != NULL) {
+ wc_SlhDsaKey_Free(key);
+ }
+
+ LogStr("wc_SlhDsaKey_Free(key=%p)\n", key);
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1get_1param
+ (JNIEnv* env, jobject this)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ SlhDsaKey* key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return -1;
+ }
+
+ if (key == NULL || key->params == NULL) {
+ return -1;
+ }
+
+ return (jint)key->params->param;
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+ return -1;
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1make_1key
+ (JNIEnv* env, jobject this, jobject rng_object)
+{
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_MAKE_KEY)
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ WC_RNG* rng = NULL;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ rng = (WC_RNG*) getNativeStruct(env, rng_object);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ if (key == NULL || rng == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_MakeKey(key, rng);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_MakeKey(key=%p) = %d\n", key, ret);
+#else
+ (void)env;
+ (void)this;
+ (void)rng_object;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1make_1key_1with_1seeds
+ (JNIEnv* env, jobject this, jbyteArray skSeed_object, jbyteArray skPrf_object, jbyteArray pkSeed_object)
+{
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_MAKE_KEY)
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* skSeed = NULL;
+ byte* skPrf = NULL;
+ byte* pkSeed = NULL;
+ word32 skSeedLen = 0;
+ word32 skPrfLen = 0;
+ word32 pkSeedLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ if (skSeed_object != NULL) {
+ skSeed = getByteArray(env, skSeed_object);
+ skSeedLen = getByteArrayLength(env, skSeed_object);
+ }
+
+ if (skPrf_object != NULL) {
+ skPrf = getByteArray(env, skPrf_object);
+ skPrfLen = getByteArrayLength(env, skPrf_object);
+ }
+
+ if (pkSeed_object != NULL) {
+ pkSeed = getByteArray(env, pkSeed_object);
+ pkSeedLen = getByteArrayLength(env, pkSeed_object);
+ }
+
+ if ((skSeed_object != NULL && skSeed == NULL) ||
+ (skPrf_object != NULL && skPrf == NULL) ||
+ (pkSeed_object != NULL && pkSeed == NULL)) {
+
+ if (skSeed != NULL) {
+ releaseByteArray(env, skSeed_object, skSeed, JNI_ABORT);
+ }
+ if (skPrf != NULL) {
+ releaseByteArray(env, skPrf_object, skPrf, JNI_ABORT);
+ }
+ if (pkSeed != NULL) {
+ releaseByteArray(env, pkSeed_object, pkSeed, JNI_ABORT);
+ }
+ return;
+ }
+
+ if (key == NULL || skSeed == NULL || skPrf == NULL || pkSeed == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_MakeKeyWithRandom(key, skSeed, skSeedLen,
+ skPrf, skPrfLen, pkSeed, pkSeedLen);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_MakeKeyWithRandom(key=%p) = %d\n", key, ret);
+
+ if (skSeed_object != NULL) {
+ releaseByteArray(env, skSeed_object, skSeed, JNI_ABORT);
+ }
+ if (skPrf_object != NULL) {
+ releaseByteArray(env, skPrf_object, skPrf, JNI_ABORT);
+ }
+ if (pkSeed_object != NULL) {
+ releaseByteArray(env, pkSeed_object, pkSeed, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)skSeed_object;
+ (void)skPrf_object;
+ (void)pkSeed_object;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign
+ (JNIEnv* env, jobject this, jbyteArray ctx_object, jbyteArray msg_object, jobject rng_object)
+{
+ jbyteArray result = NULL;
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_SIGN)
+ int ret = 0;
+ int sigSz = 0;
+ SlhDsaKey* key = NULL;
+ WC_RNG* rng = NULL;
+ byte* ctx = NULL;
+ byte* msg = NULL;
+ byte* sig = NULL;
+ word32 ctxLen = 0;
+ word32 msgLen = 0;
+ word32 sigLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ rng = (WC_RNG*) getNativeStruct(env, rng_object);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL || rng == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ /* ctx_object may be null for an empty context. */
+ if (ctx_object != NULL) {
+ ctx = getByteArray(env, ctx_object);
+ ctxLen = getByteArrayLength(env, ctx_object);
+ }
+
+ if (msg_object != NULL) {
+ msg = getByteArray(env, msg_object);
+ msgLen = getByteArrayLength(env, msg_object);
+ }
+
+ if ((ctx_object != NULL && ctx == NULL) ||
+ (msg_object != NULL && msg == NULL)) {
+ if (ctx != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+ return NULL;
+ }
+
+ /* FIPS 205 caps context length at 255 bytes (also enforced in Java). */
+ if (ctxLen > com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ sigSz = wc_SlhDsaKey_SigSize(key);
+ if (sigSz < 0) {
+ ret = sigSz;
+ }
+ else {
+ sigLen = (word32)sigSz;
+ }
+ }
+
+ if (ret == 0) {
+ sig = (byte*)XMALLOC(sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (sig == NULL) {
+ ret = MEMORY_E;
+ }
+ else {
+ XMEMSET(sig, 0, sigLen);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_Sign(key, ctx, (byte)ctxLen, msg, msgLen,
+ sig, &sigLen, rng);
+ }
+
+ if (ret == 0) {
+ result = (*env)->NewByteArray(env, sigLen);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, sigLen,
+ (const jbyte*)sig);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate sig");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_Sign(key=%p) = %d\n", key, ret);
+
+ if (sig != NULL) {
+ XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ }
+ if (ctx_object != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg_object != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)ctx_object;
+ (void)msg_object;
+ (void)rng_object;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign_1msg_1prehash
+ (JNIEnv* env, jobject this, jbyteArray ctx_object, jbyteArray msg_object, jobject rng_object)
+{
+ jbyteArray result = NULL;
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_SIGN)
+ int ret = 0;
+ int sigSz = 0;
+ SlhDsaKey* key = NULL;
+ WC_RNG* rng = NULL;
+ byte* ctx = NULL;
+ byte* msg = NULL;
+ byte* sig = NULL;
+ word32 ctxLen = 0;
+ word32 msgLen = 0;
+ word32 sigLen = 0;
+ byte digest[WC_JNI_SLHDSA_MAX_PH_LEN];
+ word32 digestLen = 0;
+ enum wc_HashType hashType = WC_HASH_TYPE_NONE;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ rng = (WC_RNG*) getNativeStruct(env, rng_object);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL || rng == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ /* ctx_object may be null for an empty context. */
+ if (ctx_object != NULL) {
+ ctx = getByteArray(env, ctx_object);
+ ctxLen = getByteArrayLength(env, ctx_object);
+ }
+
+ if (msg_object != NULL) {
+ msg = getByteArray(env, msg_object);
+ msgLen = getByteArrayLength(env, msg_object);
+ }
+
+ if ((ctx_object != NULL && ctx == NULL) ||
+ (msg_object != NULL && msg == NULL)) {
+ if (ctx != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+ return NULL;
+ }
+
+ /* FIPS 205 caps context length at 255 bytes (also enforced in Java). */
+ if (ctxLen > com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ ret = slhdsa_prehash_msg(key, msg, msgLen, digest, &digestLen,
+ &hashType);
+ }
+
+ if (ret == 0) {
+ sigSz = wc_SlhDsaKey_SigSize(key);
+ if (sigSz < 0) {
+ ret = sigSz;
+ }
+ else {
+ sigLen = (word32)sigSz;
+ }
+ }
+
+ if (ret == 0) {
+ sig = (byte*)XMALLOC(sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (sig == NULL) {
+ ret = MEMORY_E;
+ }
+ else {
+ XMEMSET(sig, 0, sigLen);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_SignHash(key, ctx, (byte)ctxLen, digest, digestLen,
+ hashType, sig, &sigLen, rng);
+ }
+
+ if (ret == 0) {
+ result = (*env)->NewByteArray(env, sigLen);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, sigLen,
+ (const jbyte*)sig);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate sig");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_SignHash(prehash, key=%p) = %d\n", key, ret);
+
+ if (sig != NULL) {
+ XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ }
+ if (ctx_object != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg_object != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)ctx_object;
+ (void)msg_object;
+ (void)rng_object;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign_1deterministic
+ (JNIEnv* env, jobject this, jbyteArray ctx_object, jbyteArray msg_object)
+{
+ jbyteArray result = NULL;
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_SIGN)
+ int ret = 0;
+ int sigSz = 0;
+ SlhDsaKey* key = NULL;
+ byte* ctx = NULL;
+ byte* msg = NULL;
+ byte* sig = NULL;
+ word32 ctxLen = 0;
+ word32 msgLen = 0;
+ word32 sigLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ if (ctx_object != NULL) {
+ ctx = getByteArray(env, ctx_object);
+ ctxLen = getByteArrayLength(env, ctx_object);
+ }
+
+ if (msg_object != NULL) {
+ msg = getByteArray(env, msg_object);
+ msgLen = getByteArrayLength(env, msg_object);
+ }
+
+ if ((ctx_object != NULL && ctx == NULL) ||
+ (msg_object != NULL && msg == NULL)) {
+ if (ctx != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+ return NULL;
+ }
+
+ /* FIPS 205 caps context length at 255 bytes (also enforced in Java). */
+ if (ctxLen > com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ sigSz = wc_SlhDsaKey_SigSize(key);
+ if (sigSz < 0) {
+ ret = sigSz;
+ }
+ else {
+ sigLen = (word32)sigSz;
+ }
+ }
+
+ if (ret == 0) {
+ sig = (byte*)XMALLOC(sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (sig == NULL) {
+ ret = MEMORY_E;
+ }
+ else {
+ XMEMSET(sig, 0, sigLen);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_SignDeterministic(key, ctx, (byte)ctxLen, msg,
+ msgLen, sig, &sigLen);
+ }
+
+ if (ret == 0) {
+ result = (*env)->NewByteArray(env, sigLen);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, sigLen,
+ (const jbyte*)sig);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate sig");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_SignDeterministic(key=%p) = %d\n", key, ret);
+
+ if (sig != NULL) {
+ XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ }
+ if (ctx_object != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg_object != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)ctx_object;
+ (void)msg_object;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sign_1hash
+ (JNIEnv* env, jobject this, jbyteArray ctx_object, jint hashAlg, jbyteArray hash_object, jobject rng_object)
+{
+ jbyteArray result = NULL;
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_SIGN)
+ int ret = 0;
+ int sigSz = 0;
+ SlhDsaKey* key = NULL;
+ WC_RNG* rng = NULL;
+ byte* ctx = NULL;
+ byte* hash = NULL;
+ byte* sig = NULL;
+ word32 ctxLen = 0;
+ word32 hashLen = 0;
+ word32 sigLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ rng = (WC_RNG*) getNativeStruct(env, rng_object);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL || rng == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ if (ctx_object != NULL) {
+ ctx = getByteArray(env, ctx_object);
+ ctxLen = getByteArrayLength(env, ctx_object);
+ }
+
+ if (hash_object != NULL) {
+ hash = getByteArray(env, hash_object);
+ hashLen = getByteArrayLength(env, hash_object);
+ }
+
+ if ((ctx_object != NULL && ctx == NULL) ||
+ (hash_object != NULL && hash == NULL)) {
+ if (ctx != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (hash != NULL) {
+ releaseByteArray(env, hash_object, hash, JNI_ABORT);
+ }
+ return NULL;
+ }
+
+ /* FIPS 205 caps context length at 255 bytes. */
+ if (ctxLen > com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ sigSz = wc_SlhDsaKey_SigSize(key);
+ if (sigSz < 0) {
+ ret = sigSz;
+ }
+ else {
+ sigLen = (word32)sigSz;
+ }
+ }
+
+ if (ret == 0) {
+ sig = (byte*)XMALLOC(sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (sig == NULL) {
+ ret = MEMORY_E;
+ }
+ else {
+ XMEMSET(sig, 0, sigLen);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_SignHash(key, ctx, (byte)ctxLen, hash, hashLen,
+ (enum wc_HashType)hashAlg, sig, &sigLen, rng);
+ }
+
+ if (ret == 0) {
+ result = (*env)->NewByteArray(env, sigLen);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, sigLen,
+ (const jbyte*)sig);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate sig");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_SignHash(key=%p) = %d\n", key, ret);
+
+ if (sig != NULL) {
+ XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ }
+ if (ctx_object != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (hash_object != NULL) {
+ releaseByteArray(env, hash_object, hash, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)ctx_object;
+ (void)hashAlg;
+ (void)hash_object;
+ (void)rng_object;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1verify
+ (JNIEnv* env, jobject this, jbyteArray sig_object, jbyteArray ctx_object, jbyteArray msg_object)
+{
+ jboolean result = JNI_FALSE;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* sig = NULL;
+ byte* ctx = NULL;
+ byte* msg = NULL;
+ word32 sigLen = 0;
+ word32 ctxLen = 0;
+ word32 msgLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return JNI_FALSE;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return JNI_FALSE;
+ }
+
+ if (sig_object != NULL) {
+ sig = getByteArray(env, sig_object);
+ sigLen = getByteArrayLength(env, sig_object);
+ }
+
+ if (ctx_object != NULL) {
+ ctx = getByteArray(env, ctx_object);
+ ctxLen = getByteArrayLength(env, ctx_object);
+ }
+
+ if (msg_object != NULL) {
+ msg = getByteArray(env, msg_object);
+ msgLen = getByteArrayLength(env, msg_object);
+ }
+
+ if ((sig_object != NULL && sig == NULL) ||
+ (ctx_object != NULL && ctx == NULL) ||
+ (msg_object != NULL && msg == NULL)) {
+ if (sig != NULL) {
+ releaseByteArray(env, sig_object, sig, JNI_ABORT);
+ }
+ if (ctx != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+ return JNI_FALSE;
+ }
+
+ /* FIPS 205 caps context length at 255 bytes. */
+ if (ctxLen > com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_Verify(key, ctx, (byte)ctxLen, msg, msgLen,
+ sig, sigLen);
+ }
+
+ if (ret == 0) {
+ result = JNI_TRUE;
+ }
+ else if (ret != SIG_VERIFY_E && ret != BAD_LENGTH_E) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_Verify(key=%p) = %d\n", key, ret);
+
+ if (sig_object != NULL) {
+ releaseByteArray(env, sig_object, sig, JNI_ABORT);
+ }
+ if (ctx_object != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg_object != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)sig_object;
+ (void)ctx_object;
+ (void)msg_object;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1verify_1msg_1prehash
+ (JNIEnv* env, jobject this, jbyteArray sig_object, jbyteArray ctx_object, jbyteArray msg_object)
+{
+ jboolean result = JNI_FALSE;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* sig = NULL;
+ byte* ctx = NULL;
+ byte* msg = NULL;
+ word32 sigLen = 0;
+ word32 ctxLen = 0;
+ word32 msgLen = 0;
+ byte digest[WC_JNI_SLHDSA_MAX_PH_LEN];
+ word32 digestLen = 0;
+ enum wc_HashType hashType = WC_HASH_TYPE_NONE;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return JNI_FALSE;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return JNI_FALSE;
+ }
+
+ if (sig_object != NULL) {
+ sig = getByteArray(env, sig_object);
+ sigLen = getByteArrayLength(env, sig_object);
+ }
+
+ if (ctx_object != NULL) {
+ ctx = getByteArray(env, ctx_object);
+ ctxLen = getByteArrayLength(env, ctx_object);
+ }
+
+ if (msg_object != NULL) {
+ msg = getByteArray(env, msg_object);
+ msgLen = getByteArrayLength(env, msg_object);
+ }
+
+ if ((sig_object != NULL && sig == NULL) ||
+ (ctx_object != NULL && ctx == NULL) ||
+ (msg_object != NULL && msg == NULL)) {
+ if (sig != NULL) {
+ releaseByteArray(env, sig_object, sig, JNI_ABORT);
+ }
+ if (ctx != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+ return JNI_FALSE;
+ }
+
+ /* FIPS 205 caps context length at 255 bytes. */
+ if (ctxLen > com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ ret = slhdsa_prehash_msg(key, msg, msgLen, digest, &digestLen,
+ &hashType);
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_VerifyHash(key, ctx, (byte)ctxLen, digest,
+ digestLen, hashType, sig, sigLen);
+ }
+
+ if (ret == 0) {
+ result = JNI_TRUE;
+ }
+ else if (ret != SIG_VERIFY_E && ret != BAD_LENGTH_E) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_VerifyHash(prehash, key=%p) = %d\n", key, ret);
+
+ if (sig_object != NULL) {
+ releaseByteArray(env, sig_object, sig, JNI_ABORT);
+ }
+ if (ctx_object != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (msg_object != NULL) {
+ releaseByteArray(env, msg_object, msg, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)sig_object;
+ (void)ctx_object;
+ (void)msg_object;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1verify_1hash
+ (JNIEnv* env, jobject this, jbyteArray sig_object, jbyteArray ctx_object, jint hashAlg, jbyteArray hash_object)
+{
+ jboolean result = JNI_FALSE;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* sig = NULL;
+ byte* ctx = NULL;
+ byte* hash = NULL;
+ word32 sigLen = 0;
+ word32 ctxLen = 0;
+ word32 hashLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return JNI_FALSE;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return JNI_FALSE;
+ }
+
+ if (sig_object != NULL) {
+ sig = getByteArray(env, sig_object);
+ sigLen = getByteArrayLength(env, sig_object);
+ }
+
+ if (ctx_object != NULL) {
+ ctx = getByteArray(env, ctx_object);
+ ctxLen = getByteArrayLength(env, ctx_object);
+ }
+
+ if (hash_object != NULL) {
+ hash = getByteArray(env, hash_object);
+ hashLen = getByteArrayLength(env, hash_object);
+ }
+
+ if ((sig_object != NULL && sig == NULL) ||
+ (ctx_object != NULL && ctx == NULL) ||
+ (hash_object != NULL && hash == NULL)) {
+ if (sig != NULL) {
+ releaseByteArray(env, sig_object, sig, JNI_ABORT);
+ }
+ if (ctx != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (hash != NULL) {
+ releaseByteArray(env, hash_object, hash, JNI_ABORT);
+ }
+ return JNI_FALSE;
+ }
+
+ /* FIPS 205 caps context length at 255 bytes. */
+ if (ctxLen > com_wolfssl_wolfcrypt_SlhDsa_SLH_DSA_MAX_CONTEXT_LEN) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_VerifyHash(key, ctx, (byte)ctxLen, hash, hashLen,
+ (enum wc_HashType)hashAlg, sig, sigLen);
+ }
+
+ if (ret == 0) {
+ result = JNI_TRUE;
+ }
+ else if (ret != SIG_VERIFY_E && ret != BAD_LENGTH_E) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_VerifyHash(key=%p) = %d\n", key, ret);
+
+ if (sig_object != NULL) {
+ releaseByteArray(env, sig_object, sig, JNI_ABORT);
+ }
+ if (ctx_object != NULL) {
+ releaseByteArray(env, ctx_object, ctx, JNI_ABORT);
+ }
+ if (hash_object != NULL) {
+ releaseByteArray(env, hash_object, hash, JNI_ABORT);
+ }
+#else
+ (void)env;
+ (void)this;
+ (void)sig_object;
+ (void)ctx_object;
+ (void)hashAlg;
+ (void)hash_object;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1export_1public
+ (JNIEnv* env, jobject this)
+{
+ jbyteArray result = NULL;
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ int pubSz = 0;
+ SlhDsaKey* key = NULL;
+ byte* output = NULL;
+ word32 outputSz = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ pubSz = wc_SlhDsaKey_PublicSize(key);
+ if (pubSz < 0) {
+ throwWolfCryptExceptionFromError(env, pubSz);
+ return NULL;
+ }
+ outputSz = (word32)pubSz;
+
+ output = (byte*)XMALLOC(outputSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (output == NULL) {
+ throwOutOfMemoryException(env, "Failed to allocate public key buffer");
+ return NULL;
+ }
+ XMEMSET(output, 0, outputSz);
+
+ ret = wc_SlhDsaKey_ExportPublic(key, output, &outputSz);
+ if (ret == 0) {
+ result = (*env)->NewByteArray(env, outputSz);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, outputSz,
+ (const jbyte*)output);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate public key");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_ExportPublic(key=%p) = %d\n", key, ret);
+
+ XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1export_1private
+ (JNIEnv* env, jobject this)
+{
+ jbyteArray result = NULL;
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_PRIV_KEY)
+ int ret = 0;
+ int privSz = 0;
+ SlhDsaKey* key = NULL;
+ byte* output = NULL;
+ word32 outputSz = 0;
+ word32 outputBufSz = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ privSz = wc_SlhDsaKey_PrivateSize(key);
+ if (privSz < 0) {
+ throwWolfCryptExceptionFromError(env, privSz);
+ return NULL;
+ }
+ outputSz = (word32)privSz;
+ outputBufSz = outputSz;
+
+ output = (byte*)XMALLOC(outputSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (output == NULL) {
+ throwOutOfMemoryException(env, "Failed to allocate private key buffer");
+ return NULL;
+ }
+ XMEMSET(output, 0, outputSz);
+
+ ret = wc_SlhDsaKey_ExportPrivate(key, output, &outputSz);
+ if (ret == 0) {
+ result = (*env)->NewByteArray(env, outputSz);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, outputSz,
+ (const jbyte*)output);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate private key");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_ExportPrivate(key=%p) = %d\n", key, ret);
+
+ wc_ForceZero(output, outputBufSz);
+ XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1import_1public
+ (JNIEnv* env, jobject this, jbyteArray in_object)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* in = NULL;
+ word32 inLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ in = getByteArray(env, in_object);
+ inLen = getByteArrayLength(env, in_object);
+
+ if (key == NULL || in == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_ImportPublic(key, in, inLen);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_ImportPublic(key=%p) = %d\n", key, ret);
+
+ releaseByteArray(env, in_object, in, JNI_ABORT);
+#else
+ (void)env;
+ (void)this;
+ (void)in_object;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1import_1private
+ (JNIEnv* env, jobject this, jbyteArray in_object)
+{
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_PRIV_KEY)
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* in = NULL;
+ word32 inLen = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ in = getByteArray(env, in_object);
+ inLen = getByteArrayLength(env, in_object);
+
+ if (key == NULL || in == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_ImportPrivate(key, in, inLen);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_ImportPrivate(key=%p) = %d\n", key, ret);
+
+ releaseByteArray(env, in_object, in, JNI_ABORT);
+#else
+ (void)env;
+ (void)this;
+ (void)in_object;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1PublicKeyToDer
+ (JNIEnv* env, jobject this, jboolean withAlg)
+{
+ jbyteArray result = NULL;
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_ASN1_EXPORT)
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* output = NULL;
+ word32 outputSz = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ /* Call with NULL output to get required size. */
+ ret = wc_SlhDsaKey_PublicKeyToDer(key, NULL, 0, (int)withAlg);
+ if (ret <= 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ return NULL;
+ }
+ outputSz = (word32)ret;
+
+ output = (byte*)XMALLOC(outputSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (output == NULL) {
+ throwOutOfMemoryException(env, "Failed to allocate public DER buffer");
+ return NULL;
+ }
+ XMEMSET(output, 0, outputSz);
+
+ ret = wc_SlhDsaKey_PublicKeyToDer(key, output, outputSz, (int)withAlg);
+ if (ret > 0) {
+ result = (*env)->NewByteArray(env, ret);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, ret,
+ (const jbyte*)output);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate public DER");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_PublicKeyToDer(key=%p) = %d\n", key, ret);
+
+ XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+#else
+ (void)env;
+ (void)this;
+ (void)withAlg;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1KeyToDer
+ (JNIEnv* env, jobject this)
+{
+ jbyteArray result = NULL;
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_PRIV_KEY) && \
+ defined(WC_JNI_SLHDSA_HAVE_ASN1_EXPORT)
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* output = NULL;
+ word32 outputSz = 0;
+ word32 outputBufSz = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return NULL;
+ }
+
+ if (key == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return NULL;
+ }
+
+ /* Call with NULL output to get required size. */
+ ret = wc_SlhDsaKey_KeyToDer(key, NULL, 0);
+ if (ret <= 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ return NULL;
+ }
+ outputSz = (word32)ret;
+ outputBufSz = outputSz;
+
+ output = (byte*)XMALLOC(outputSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (output == NULL) {
+ throwOutOfMemoryException(env, "Failed to allocate PKCS#8 buffer");
+ return NULL;
+ }
+ XMEMSET(output, 0, outputSz);
+
+ ret = wc_SlhDsaKey_KeyToDer(key, output, outputSz);
+ if (ret > 0) {
+ result = (*env)->NewByteArray(env, ret);
+ if (result != NULL) {
+ (*env)->SetByteArrayRegion(env, result, 0, ret,
+ (const jbyte*)output);
+ }
+ else {
+ throwWolfCryptException(env, "Failed to allocate PKCS#8 DER");
+ }
+ }
+ else {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_KeyToDer(key=%p) = %d\n", key, ret);
+
+ wc_ForceZero(output, outputBufSz);
+ XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+#endif
+ return result;
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1PublicKeyDecode
+ (JNIEnv* env, jobject this, jbyteArray der_object)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* der = NULL;
+ word32 derLen = 0;
+ word32 idx = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ der = getByteArray(env, der_object);
+ derLen = getByteArrayLength(env, der_object);
+
+ if (key == NULL || der == NULL || derLen == 0) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_PublicKeyDecode(der, &idx, key, derLen);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_PublicKeyDecode(key=%p) = %d\n", key, ret);
+
+ releaseByteArray(env, der_object, der, JNI_ABORT);
+#else
+ (void)env;
+ (void)this;
+ (void)der_object;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1PrivateKeyDecode
+ (JNIEnv* env, jobject this, jbyteArray der_object)
+{
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_PRIV_KEY)
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+ byte* der = NULL;
+ byte* derCopy = NULL;
+ word32 derLen = 0;
+ word32 idx = 0;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ der = getByteArray(env, der_object);
+ derLen = getByteArrayLength(env, der_object);
+
+ if (key == NULL || der == NULL || derLen == 0) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ /* Copy because PrivateKeyDecode may modify the input buffer. */
+ derCopy = (byte*)XMALLOC(derLen, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (derCopy == NULL) {
+ ret = MEMORY_E;
+ }
+ else {
+ XMEMCPY(derCopy, der, derLen);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_SlhDsaKey_PrivateKeyDecode(derCopy, &idx, key, derLen);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_PrivateKeyDecode(key=%p) = %d\n", key, ret);
+
+ if (derCopy != NULL) {
+ wc_ForceZero(derCopy, derLen);
+ XFREE(derCopy, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ }
+ releaseByteArray(env, der_object, der, JNI_ABORT);
+#else
+ (void)env;
+ (void)this;
+ (void)der_object;
+ throwNotCompiledInException(env);
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1pub_1size
+ (JNIEnv* env, jobject this)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return 0;
+ }
+
+ if (key == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_PublicSize(key);
+ }
+
+ if (ret < 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ return 0;
+ }
+ return (jint)ret;
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1priv_1size
+ (JNIEnv* env, jobject this)
+{
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_PRIV_KEY)
+ int ret = 0;
+ SlhDsaKey* key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return 0;
+ }
+
+ if (key == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_PrivateSize(key);
+ }
+
+ if (ret < 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ return 0;
+ }
+ return (jint)ret;
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+ return 0;
+#endif
+}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1sig_1size
+ (JNIEnv* env, jobject this)
+{
+#ifdef WOLFSSL_HAVE_SLHDSA
+ int ret = 0;
+ SlhDsaKey* key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return 0;
+ }
+
+ if (key == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_SigSize(key);
+ }
+
+ if (ret < 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ return 0;
+ }
+ return (jint)ret;
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+ return 0;
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1check_1key
+ (JNIEnv* env, jobject this)
+{
+#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WC_JNI_SLHDSA_HAVE_PRIV_KEY)
+ int ret = 0;
+ SlhDsaKey* key = NULL;
+
+ key = (SlhDsaKey*) getNativeStruct(env, this);
+ if ((*env)->ExceptionOccurred(env)) {
+ return;
+ }
+
+ if (key == NULL) {
+ ret = BAD_FUNC_ARG;
+ }
+ else {
+ ret = wc_SlhDsaKey_CheckKey(key);
+ }
+
+ if (ret != 0) {
+ throwWolfCryptExceptionFromError(env, ret);
+ }
+
+ LogStr("wc_SlhDsaKey_CheckKey(key=%p) = %d\n", key, ret);
+#else
+ (void)env;
+ (void)this;
+ throwNotCompiledInException(env);
+#endif
+}
diff --git a/makefile.linux b/makefile.linux
index da377f36..38af0642 100644
--- a/makefile.linux
+++ b/makefile.linux
@@ -35,7 +35,7 @@ OBJ_LIST = jni_fips.o jni_native_struct.o jni_pwdbased.o jni_aes.o \
jni_wolfcrypt.o jni_wolfssl_cert_manager.o \
jni_wolfssl_x509_store_ctx.o jni_jce_wolfsslkeystore.o \
jni_mldsa.o \
- jni_mlkem.o jni_xmss.o jni_lms.o
+ jni_mlkem.o jni_xmss.o jni_lms.o jni_slhdsa.o
OBJS = $(patsubst %,$(OUT_PATH)/%,$(OBJ_LIST))
TARGET = $(OUT_PATH)/libwolfcryptjni.so
diff --git a/makefile.macosx b/makefile.macosx
index a6608356..fc81e2ac 100644
--- a/makefile.macosx
+++ b/makefile.macosx
@@ -29,7 +29,7 @@ OBJ_LIST = jni_fips.o jni_native_struct.o jni_pwdbased.o jni_aes.o \
jni_wolfcrypt.o jni_wolfssl_cert_manager.o \
jni_wolfssl_x509_store_ctx.o jni_jce_wolfsslkeystore.o \
jni_mldsa.o \
- jni_mlkem.o jni_xmss.o jni_lms.o
+ jni_mlkem.o jni_xmss.o jni_lms.o jni_slhdsa.o
OBJS = $(patsubst %,$(OUT_PATH)/%,$(OBJ_LIST))
TARGET = $(OUT_PATH)/libwolfcryptjni.dylib
diff --git a/scripts/infer.sh b/scripts/infer.sh
index 908793da..8156a961 100755
--- a/scripts/infer.sh
+++ b/scripts/infer.sh
@@ -78,6 +78,7 @@ infer --fail-on-issue run -- javac \
src/main/java/com/wolfssl/wolfcrypt/Sha384.java \
src/main/java/com/wolfssl/wolfcrypt/Sha512.java \
src/main/java/com/wolfssl/wolfcrypt/Sha3.java \
+ src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java \
src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java \
src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java \
src/main/java/com/wolfssl/wolfcrypt/WolfCryptException.java \
@@ -90,6 +91,7 @@ infer --fail-on-issue run -- javac \
src/main/java/com/wolfssl/provider/jce/WolfCryptAesParameters.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptASN1Util.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java \
+ src/main/java/com/wolfssl/provider/jce/WolfCryptContextParameterSpec.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptDebug.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameterGenerator.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameters.java \
@@ -111,7 +113,6 @@ infer --fail-on-issue run -- javac \
src/main/java/com/wolfssl/provider/jce/WolfCryptLmsKeyFactory.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptLmsPublicKey.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptLmsSignature.java \
- src/main/java/com/wolfssl/provider/jce/WolfCryptLmsUtil.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptMac.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaKeyFactory.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaPrivateKey.java \
@@ -140,11 +141,15 @@ infer --fail-on-issue run -- javac \
src/main/java/com/wolfssl/provider/jce/WolfCryptSecretKey.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptSecretKeyFactory.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java \
+ src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaKeyFactory.java \
+ src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPrivateKey.java \
+ src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPublicKey.java \
+ src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaSignature.java \
+ src/main/java/com/wolfssl/provider/jce/WolfCryptSpkiUtil.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptXmssKeyFactory.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptXmssPublicKey.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptXmssSignature.java \
- src/main/java/com/wolfssl/provider/jce/WolfCryptXmssUtil.java \
src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java \
examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java \
examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java \
diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml
index a973b792..3c4df53c 100644
--- a/spotbugs-exclude.xml
+++ b/spotbugs-exclude.xml
@@ -158,6 +158,11 @@
+
+
+
+
+
@@ -253,6 +258,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -318,6 +333,11 @@
+
+
+
+
+
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptContextParameterSpec.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptContextParameterSpec.java
new file mode 100644
index 00000000..980abbe1
--- /dev/null
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptContextParameterSpec.java
@@ -0,0 +1,105 @@
+/* WolfCryptContextParameterSpec.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce;
+
+import java.util.Arrays;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * wolfJCE context-string parameter spec.
+ *
+ * Carries an application context string for signature schemes that accept
+ * one (for example SLH-DSA / FIPS 205, which allows a 0..255 byte context).
+ * Set it on a {@link java.security.Signature} via
+ * {@code Signature.setParameter(AlgorithmParameterSpec)}. The context may be
+ * set before or after {@code initSign}/{@code initVerify}. It persists across
+ * init calls until replaced. The signer and verifier must agree on the same
+ * context or verification fails. The default is an empty context, which
+ * matches X.509, CMS, and TLS usage.
+ */
+public class WolfCryptContextParameterSpec implements AlgorithmParameterSpec {
+
+ /** Maximum context length in bytes (FIPS 205). */
+ public static final int MAX_CONTEXT_LEN = 255;
+
+ /** Context bytes, never null (empty array for an empty context). */
+ private final byte[] context;
+
+ /**
+ * Create a context parameter spec.
+ *
+ * @param context context bytes, may be null (treated as empty) and must
+ * be at most {@link #MAX_CONTEXT_LEN} bytes
+ *
+ * @throws IllegalArgumentException if {@code context} exceeds 255 bytes
+ */
+ public WolfCryptContextParameterSpec(byte[] context)
+ throws IllegalArgumentException {
+
+ if (context == null) {
+ this.context = new byte[0];
+ }
+ else {
+ if (context.length > MAX_CONTEXT_LEN) {
+ throw new IllegalArgumentException(
+ "Context length exceeds " + MAX_CONTEXT_LEN + " bytes");
+ }
+ this.context = context.clone();
+ }
+ }
+
+ /**
+ * Get the context bytes.
+ *
+ * @return a copy of the context bytes (empty array for an empty context)
+ */
+ public byte[] getContext() {
+ return this.context.clone();
+ }
+
+ @Override
+ public String toString() {
+ return "WolfCryptContextParameterSpec(len=" + this.context.length + ")";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ WolfCryptContextParameterSpec other;
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WolfCryptContextParameterSpec)) {
+ return false;
+ }
+ other = (WolfCryptContextParameterSpec) obj;
+
+ return Arrays.equals(this.context, other.context);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(this.context);
+ }
+}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java
index 713432ff..f597fc9f 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java
@@ -57,6 +57,7 @@
import com.wolfssl.wolfcrypt.Dh;
import com.wolfssl.wolfcrypt.MlDsa;
import com.wolfssl.wolfcrypt.MlKem;
+import com.wolfssl.wolfcrypt.SlhDsa;
import com.wolfssl.wolfcrypt.Rng;
import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.WolfCryptException;
@@ -72,7 +73,8 @@ enum KeyType {
WC_ECC,
WC_DH,
WC_ML_DSA,
- WC_ML_KEM
+ WC_ML_KEM,
+ WC_SLH_DSA
}
private KeyType type = null;
@@ -84,36 +86,32 @@ enum KeyType {
private byte[] dhP = null;
private byte[] dhG = null;
- /**
- * ML-DSA parameter level ({@link MlDsa#ML_DSA_44} / {@code ML_DSA_65} /
- * {@code ML_DSA_87}), or 0 if unset. Set in the constructor and
- * updated by {@code initialize(AlgorithmParameterSpec)}.
- */
- private int mlDsaLevel = 0;
+ /** Flag for "no PQC parameter set" in pqcParam and pqcLockedParam.
+ * Must be negative, 0 reserved for SLH-DSA-SHAKE-128s. */
+ private static final int PQC_PARAM_UNSET = -1;
/**
- * For per-level {@code wcKeyPairGenMlDsa{44,65,87}} aliases, the level
- * is fixed and {@code engineInitialize(AlgorithmParameterSpec)} must
- * reject mismatching specs. 0 means the generic alias (any level via
- * spec). Set via constructor by per-level subclasses.
- */
- private int mlDsaLockedLevel = 0;
+ * PQC parameter set for WC_ML_DSA, WC_ML_KEM, and WC_SLH_DSA generators.
+ * Interpreted by this.type: an MlDsa.ML_DSA_* level, MlKem.ML_KEM_* level,
+ * or SlhDsa.SLH_DSA_* parameter set. Set to the per-family default in
+ * constructor. */
+ private int pqcParam = PQC_PARAM_UNSET;
/**
- * ML-KEM parameter level ({@link MlKem#ML_KEM_512} / {@code ML_KEM_768} /
- * {@code ML_KEM_1024}). Set in the constructor and updated by
- * {@code initialize(AlgorithmParameterSpec)}.
+ * For per-parameter-set PQC subclasses wcKeyPairGenMlDsa{44,65,87},
+ * wcKeyPairGenMlKem{512,768,1024}, wcKeyPairGenSlhDsa*, the parameter set
+ * is fixed and engineInitialize(AlgorithmParameterSpec) must reject
+ * mismatching specs. PQC_PARAM_UNSET means the generic alias (any param
+ * set via spec).
*/
- private int mlkemLevel = MlKem.ML_KEM_768;
+ private int pqcLockedParam = PQC_PARAM_UNSET;
/**
- * For per-level {@code wcKeyPairGenMlKem{512,768,1024}} aliases, the
- * level is fixed and {@code engineInitialize(AlgorithmParameterSpec)}
- * must reject mismatching specs. 0 means the generic alias (any level
- * via spec, defaulting to ML-KEM-768). Set via constructor by per-level
- * subclasses.
+ * True when the PQC parameter set was explicitly chosen, either by a
+ * per-set subclass or an engineInitialize(AlgorithmParameterSpec) call.
+ * When false, the generic generator is using its per-family default.
*/
- private int mlkemLockedLevel = 0;
+ private boolean pqcParamExplicit = false;
private Rng rng = null;
@@ -124,19 +122,20 @@ enum KeyType {
private String algString;
private WolfCryptKeyPairGenerator(KeyType type) {
- this(type, 0);
+ this(type, PQC_PARAM_UNSET);
}
/**
- * Create new WolfCryptKeyPairGenerator, optionally locking the parameter
- * set level for the per-level PQC subclasses (wcKeyPairGenMlDsa{44,65,87}
- * and wcKeyPairGenMlKem{512,768,1024}).
+ * Create new WolfCryptKeyPairGenerator, optionally locking the PQC param
+ * set for the per-parameter-set subclasses (wcKeyPairGenMlDsa{44,65,87},
+ * wcKeyPairGenMlKem{512,768,1024}, and wcKeyPairGenSlhDsa*).
*
* @param type key type to generate
- * @param lockedLevel fixed PQC parameter set level for per-level aliases,
- * or 0 for the generic alias (any level via spec)
+ * @param lockedParam fixed PQC parameter set for per-set aliases, or
+ * PQC_PARAM_UNSET for the generic alias (any parameter set via
+ * spec). Ignored for non-PQC key types.
*/
- private WolfCryptKeyPairGenerator(KeyType type, int lockedLevel) {
+ private WolfCryptKeyPairGenerator(KeyType type, int lockedParam) {
this.type = type;
@@ -180,28 +179,38 @@ private WolfCryptKeyPairGenerator(KeyType type, int lockedLevel) {
}
}
- /* Set defaults for ML-DSA key generation. Per-level subclasses
- * pass their fixed level in, the generic alias defaults to
- * ML-DSA-65, matching JDK 24 / JEP 497 default. */
- if (type == KeyType.WC_ML_DSA) {
- this.mlDsaLockedLevel = lockedLevel;
- this.mlDsaLevel = (lockedLevel != 0) ?
- lockedLevel : MlDsa.ML_DSA_65;
- }
+ /* Set defaults for PQC key generation (ML-DSA, ML-KEM, SLH-DSA).
+ * Per-set subclasses pass their fixed parameter set in. Generic
+ * aliases get the per-family default. */
+ if (type == KeyType.WC_ML_DSA || type == KeyType.WC_ML_KEM ||
+ type == KeyType.WC_SLH_DSA) {
- /* Set defaults for ML-KEM key generation. Per-level subclasses pass
- * their fixed level in. The generic alias defaults to ML-KEM-768,
- * matching the JDK reference implementation default. */
- if (type == KeyType.WC_ML_KEM) {
- this.mlkemLockedLevel = lockedLevel;
- this.mlkemLevel = (lockedLevel != 0) ?
- lockedLevel : MlKem.ML_KEM_768;
+ this.pqcLockedParam = lockedParam;
+ if (lockedParam != PQC_PARAM_UNSET) {
+ this.pqcParam = lockedParam;
+ this.pqcParamExplicit = true;
+ }
+ else {
+ switch (type) {
+ case WC_ML_DSA:
+ this.pqcParam = MlDsa.ML_DSA_65;
+ break;
+ case WC_ML_KEM:
+ this.pqcParam = MlKem.ML_KEM_768;
+ break;
+ default:
+ this.pqcParam = SlhDsa.SLH_DSA_SHA2_128F;
+ break;
+ }
+ this.pqcParamExplicit = false;
+ }
}
/* Initialize RNG for default key generation if needed. */
if (type == KeyType.WC_RSA || type == KeyType.WC_RSA_PSS ||
type == KeyType.WC_ECC || type == KeyType.WC_DH ||
- type == KeyType.WC_ML_DSA || type == KeyType.WC_ML_KEM) {
+ type == KeyType.WC_ML_DSA || type == KeyType.WC_ML_KEM ||
+ type == KeyType.WC_SLH_DSA) {
synchronized (rngLock) {
if (this.rng == null) {
@@ -219,26 +228,16 @@ private WolfCryptKeyPairGenerator(KeyType type, int lockedLevel) {
@Override
public synchronized void initialize(int keysize, SecureRandom random) {
- if (type == KeyType.WC_ML_DSA) {
- /* ML-DSA parameter sets (44/65/87) are not selected by integer
- * key size. Callers must supply a NamedParameterSpec or a
- * WolfPQCParameterSpec via initialize(AlgorithmParameterSpec).
- * Matches JDK 24 / JEP 497 behavior. */
- throw new InvalidParameterException(
- "ML-DSA does not accept integer key sizes: use " +
- "initialize(AlgorithmParameterSpec) with a " +
- "NamedParameterSpec or WolfPQCParameterSpec instead.");
- }
-
- if (type == KeyType.WC_ML_KEM) {
- /* ML-KEM parameter sets (512/768/1024) are selected by a named
- * parameter spec, not an integer key size. Use a
- * NamedParameterSpec or WolfPQCParameterSpec via
- * initialize(AlgorithmParameterSpec), or the parameter-set-
- * specific KeyPairGenerator name (ML-KEM-512/768/1024). */
+ if (type == KeyType.WC_ML_DSA || type == KeyType.WC_ML_KEM ||
+ type == KeyType.WC_SLH_DSA) {
+ /* PQC parameter sets are selected by a named parameter spec, not
+ * integer key size. Callers must supply a NamedParameterSpec or
+ * WolfPQCParameterSpec via initialize(AlgorithmParameterSpec), or
+ * use the parameter-set-specific KeyPairGenerator name. Matches
+ * JDK 24 / JEP 497 behavior. */
throw new InvalidParameterException(
- "ML-KEM does not accept integer key sizes: use " +
- "initialize(AlgorithmParameterSpec) with a " +
+ typeToString(this.type) + " does not accept integer key " +
+ "sizes: use initialize(AlgorithmParameterSpec) with a " +
"NamedParameterSpec or WolfPQCParameterSpec instead.");
}
@@ -490,94 +489,53 @@ public synchronized void initialize(AlgorithmParameterSpec params,
break;
case WC_ML_DSA:
-
- int newLevel;
- String mlDsaName = null;
-
- /* Fall back to our own AlgorithmParameterSpec subtype
- * for JDK 8-10. */
- if (params instanceof WolfPQCParameterSpec) {
- mlDsaName = ((WolfPQCParameterSpec) params).getName();
- }
- /* JDK 11+ uses standard NamedParameterSpec via reflection. */
- else {
- mlDsaName =
- WolfPQCJdkCompat.namedParameterSpecGetName(params);
- }
-
- if (mlDsaName == null) {
- throw new InvalidAlgorithmParameterException(
- "ML-DSA params must be a NamedParameterSpec " +
- "(JDK 11+) or WolfPQCParameterSpec (JDK 8-10), got: " +
- params.getClass().getName());
- }
-
- try {
- newLevel = WolfPQCJdkCompat.paramNameToLevel(mlDsaName);
- }
- catch (IllegalArgumentException e) {
- throw new InvalidAlgorithmParameterException(
- "Unrecognized ML-DSA parameter set: " + mlDsaName);
- }
-
- /* Per-level wcKeyPairGenMlDsa{44,65,87} aliases reject specs
- * that don't match their locked level. */
- if (this.mlDsaLockedLevel != 0 &&
- newLevel != this.mlDsaLockedLevel) {
-
- throw new InvalidAlgorithmParameterException(
- "Spec '" + mlDsaName + "' does not match the " +
- "fixed parameter set for this KeyPairGenerator");
- }
-
- this.mlDsaLevel = newLevel;
- log("init with ML-DSA spec: " + mlDsaName);
-
- break;
-
case WC_ML_KEM:
+ case WC_SLH_DSA:
- int mlkemNewLevel;
- String mlkemName = null;
+ String algName = typeToString(this.type);
+ int newParam;
+ String paramName = null;
- /* Fall back to our own AlgorithmParameterSpec for JDK 8-10 */
+ /* Fall back to our AlgorithmParameterSpec subtype for
+ * JDK 8-10. */
if (params instanceof WolfPQCParameterSpec) {
- mlkemName = ((WolfPQCParameterSpec) params).getName();
+ paramName = ((WolfPQCParameterSpec) params).getName();
}
- /* JDK 11+ uses standard NamedParameterSpec via reflection */
+ /* JDK 11+ uses standard NamedParameterSpec via reflection. */
else {
- mlkemName =
+ paramName =
WolfPQCJdkCompat.namedParameterSpecGetName(params);
}
- if (mlkemName == null) {
+ if (paramName == null) {
throw new InvalidAlgorithmParameterException(
- "ML-KEM params must be a NamedParameterSpec " +
+ algName + " params must be a NamedParameterSpec " +
"(JDK 11+) or WolfPQCParameterSpec (JDK 8-10), got: " +
params.getClass().getName());
}
try {
- mlkemNewLevel =
- WolfPQCJdkCompat.mlkemParamNameToLevel(mlkemName);
+ newParam = pqcNameToParam(paramName);
}
catch (IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(
- "Unrecognized ML-KEM parameter set: " + mlkemName);
+ "Unrecognized " + algName + " parameter set: " +
+ paramName);
}
- /* Per-level wcKeyPairGenMlKem{512,768,1024} aliases reject
- * specs that don't match their locked level. */
- if (this.mlkemLockedLevel != 0 &&
- mlkemNewLevel != this.mlkemLockedLevel) {
+ /* Per-parameter-set subclasses reject specs that don't
+ * match their locked parameter set. */
+ if ((this.pqcLockedParam != PQC_PARAM_UNSET) &&
+ (newParam != this.pqcLockedParam)) {
throw new InvalidAlgorithmParameterException(
- "Spec '" + mlkemName + "' does not match the " +
+ "Spec '" + paramName + "' does not match the " +
"fixed parameter set for this KeyPairGenerator");
}
- this.mlkemLevel = mlkemNewLevel;
- log("init with ML-KEM spec: " + mlkemName);
+ this.pqcParam = newParam;
+ this.pqcParamExplicit = true;
+ log("init with " + algName + " spec: " + paramName);
break;
@@ -850,7 +808,7 @@ public synchronized KeyPair generateKeyPair() {
case WC_ML_DSA:
- if (this.mlDsaLevel == 0) {
+ if (this.pqcParam == PQC_PARAM_UNSET) {
throw new RuntimeException(
"ML-DSA level not set, call initialize() first");
}
@@ -860,7 +818,7 @@ public synchronized KeyPair generateKeyPair() {
byte[] mlDsaPrivDer = null;
try {
- mlDsa = new MlDsa(this.mlDsaLevel);
+ mlDsa = new MlDsa(this.pqcParam);
synchronized (rngLock) {
mlDsa.makeKey(this.rng);
@@ -871,10 +829,10 @@ public synchronized KeyPair generateKeyPair() {
WolfCryptMlDsaPublicKey mlDsaPub =
new WolfCryptMlDsaPublicKey(mlDsaPubDer,
- this.mlDsaLevel);
+ this.pqcParam);
WolfCryptMlDsaPrivateKey mlDsaPriv =
new WolfCryptMlDsaPrivateKey(mlDsaPrivDer,
- this.mlDsaLevel);
+ this.pqcParam);
pair = new KeyPair(mlDsaPub, mlDsaPriv);
@@ -890,7 +848,61 @@ public synchronized KeyPair generateKeyPair() {
}
}
- log("generated ML-DSA KeyPair, level " + this.mlDsaLevel);
+ log("generated ML-DSA KeyPair, level " + this.pqcParam);
+
+ break;
+
+ case WC_SLH_DSA:
+
+ if (this.pqcParam == PQC_PARAM_UNSET) {
+ throw new RuntimeException(
+ "SLH-DSA param set not set, call initialize() first");
+ }
+
+ /* For generic generator, fall back from default to a param set
+ * compiled into native wolfSSL, so getInstance("SLH-DSA") works
+ * on varied build configs. */
+ int slhDsaGenParam = this.pqcParam;
+ if (!this.pqcParamExplicit) {
+ slhDsaGenParam = resolveDefaultSlhDsaParam();
+ }
+
+ SlhDsa slhDsa = null;
+ byte[] slhDsaPubDer = null;
+ byte[] slhDsaPrivDer = null;
+
+ try {
+ slhDsa = new SlhDsa(slhDsaGenParam);
+
+ synchronized (rngLock) {
+ slhDsa.makeKey(this.rng);
+ }
+
+ slhDsaPubDer = slhDsa.exportPublicKeyDer(true);
+ slhDsaPrivDer = slhDsa.exportPrivateKeyDer();
+
+ WolfCryptSlhDsaPublicKey slhDsaPub =
+ new WolfCryptSlhDsaPublicKey(slhDsaPubDer,
+ slhDsaGenParam);
+ WolfCryptSlhDsaPrivateKey slhDsaPriv =
+ new WolfCryptSlhDsaPrivateKey(slhDsaPrivDer,
+ slhDsaGenParam);
+
+ pair = new KeyPair(slhDsaPub, slhDsaPriv);
+
+ }
+ catch (WolfCryptException e) {
+ throw new RuntimeException(e);
+ }
+ finally {
+ zeroArray(slhDsaPubDer);
+ zeroArray(slhDsaPrivDer);
+ if (slhDsa != null) {
+ slhDsa.releaseNativeStruct();
+ }
+ }
+
+ log("generated SLH-DSA KeyPair, param " + slhDsaGenParam);
break;
@@ -911,16 +923,16 @@ public synchronized KeyPair generateKeyPair() {
this.rng.generateBlock(MlKem.ML_KEM_SEED_SIZE);
}
- mlkem = new MlKem(this.mlkemLevel);
+ mlkem = new MlKem(this.pqcParam);
mlkem.makeKeyFromSeed(mlkemSeed);
mlkemPub = mlkem.exportPublic();
mlkemPriv = mlkem.exportPrivate();
WolfCryptMlKemPublicKey mlkemPubKey =
- new WolfCryptMlKemPublicKey(this.mlkemLevel, mlkemPub);
+ new WolfCryptMlKemPublicKey(this.pqcParam, mlkemPub);
WolfCryptMlKemPrivateKey mlkemPrivKey =
- new WolfCryptMlKemPrivateKey(this.mlkemLevel, mlkemPriv,
+ new WolfCryptMlKemPrivateKey(this.pqcParam, mlkemPriv,
mlkemSeed);
pair = new KeyPair(mlkemPubKey, mlkemPrivKey);
@@ -937,7 +949,7 @@ public synchronized KeyPair generateKeyPair() {
}
}
- log("generated ML-KEM KeyPair, level " + this.mlkemLevel);
+ log("generated ML-KEM KeyPair, level " + this.pqcParam);
break;
@@ -949,6 +961,25 @@ public synchronized KeyPair generateKeyPair() {
return pair;
}
+ /*
+ * Resolve a PQC parameter-set name to its native value for this
+ * generator's key type: an MlDsa.ML_DSA_* level, an MlKem.ML_KEM_*
+ * level, or an SlhDsa.SLH_DSA_* parameter set.
+ *
+ * Throws IllegalArgumentException if the name is not a valid parameter
+ * set name for this key type.
+ */
+ private int pqcNameToParam(String name) {
+ switch (this.type) {
+ case WC_ML_DSA:
+ return WolfPQCJdkCompat.paramNameToLevel(name);
+ case WC_ML_KEM:
+ return WolfPQCJdkCompat.mlkemParamNameToLevel(name);
+ default:
+ return WolfPQCJdkCompat.slhDsaNameToParam(name);
+ }
+ }
+
private String typeToString(KeyType type) {
switch (type) {
case WC_RSA:
@@ -963,6 +994,8 @@ private String typeToString(KeyType type) {
return "ML-DSA";
case WC_ML_KEM:
return "ML-KEM";
+ case WC_SLH_DSA:
+ return "SLH-DSA";
default:
return "None";
}
@@ -1106,6 +1139,195 @@ public wcKeyPairGenMlDsa87() {
}
}
+ /* Cached resolveDefaultSlhDsaParam() result. The parameter sets compiled
+ * into the loaded native library cannot change at runtime, so probe native
+ * availability only once. -1 means unresolved. */
+ private static volatile int defaultSlhDsaParam = -1;
+
+ /**
+ * Resolve the generic generator default SLH-DSA parameter set to one
+ * compiled into native wolfSSL. Prefers SLH-DSA-SHA2-128f, then falls back
+ * through the remaining sets so getInstance("SLH-DSA") works on varied
+ * native builds. The result is cached after the first probe.
+ *
+ * @return the first compiled-in parameter set from the preference order,
+ * or SLH-DSA-SHA2-128f if none could be probed.
+ */
+ private static int resolveDefaultSlhDsaParam() {
+
+ int resolved = defaultSlhDsaParam;
+ if (resolved >= 0) {
+ return resolved;
+ }
+
+ int[] preference = {
+ SlhDsa.SLH_DSA_SHA2_128F, SlhDsa.SLH_DSA_SHAKE_128F,
+ SlhDsa.SLH_DSA_SHA2_128S, SlhDsa.SLH_DSA_SHAKE_128S,
+ SlhDsa.SLH_DSA_SHA2_192F, SlhDsa.SLH_DSA_SHAKE_192F,
+ SlhDsa.SLH_DSA_SHA2_192S, SlhDsa.SLH_DSA_SHAKE_192S,
+ SlhDsa.SLH_DSA_SHA2_256F, SlhDsa.SLH_DSA_SHAKE_256F,
+ SlhDsa.SLH_DSA_SHA2_256S, SlhDsa.SLH_DSA_SHAKE_256S
+ };
+
+ resolved = SlhDsa.SLH_DSA_SHA2_128F;
+ for (int param : preference) {
+ if (slhDsaParamAvailable(param)) {
+ resolved = param;
+ break;
+ }
+ }
+
+ defaultSlhDsaParam = resolved;
+ return resolved;
+ }
+
+ /**
+ * Check whether an SLH-DSA parameter set is compiled into native wolfSSL.
+ * Performs a native init (via publicKeySize) without generating a key.
+ *
+ * @param param SLH-DSA parameter set (0-11)
+ *
+ * @return true if the parameter set is available, false otherwise
+ */
+ private static boolean slhDsaParamAvailable(int param) {
+
+ try {
+ SlhDsa key = new SlhDsa(param);
+ try {
+ key.publicKeySize();
+ return true;
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ catch (WolfCryptException e) {
+ return false;
+ }
+ }
+
+ /**
+ * wolfCrypt SLH-DSA key pair generator (generic). The parameter set is
+ * selectable via {@code initialize(NamedParameterSpec)} or
+ * {@code initialize(WolfPQCParameterSpec)}. Defaults to SLH-DSA-SHA2-128f,
+ * falling back to a compiled-in set on partial native builds.
+ */
+ public static final class wcKeyPairGenSlhDsa
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsa object */
+ public wcKeyPairGenSlhDsa() {
+ super(KeyType.WC_SLH_DSA, PQC_PARAM_UNSET);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHAKE-128s key pair generator. */
+ public static final class wcKeyPairGenSlhDsaShake_128s
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaShake_128s object */
+ public wcKeyPairGenSlhDsaShake_128s() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHAKE_128S);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHAKE-128f key pair generator. */
+ public static final class wcKeyPairGenSlhDsaShake_128f
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaShake_128f object */
+ public wcKeyPairGenSlhDsaShake_128f() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHAKE_128F);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHAKE-192s key pair generator. */
+ public static final class wcKeyPairGenSlhDsaShake_192s
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaShake_192s object */
+ public wcKeyPairGenSlhDsaShake_192s() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHAKE_192S);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHAKE-192f key pair generator. */
+ public static final class wcKeyPairGenSlhDsaShake_192f
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaShake_192f object */
+ public wcKeyPairGenSlhDsaShake_192f() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHAKE_192F);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHAKE-256s key pair generator. */
+ public static final class wcKeyPairGenSlhDsaShake_256s
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaShake_256s object */
+ public wcKeyPairGenSlhDsaShake_256s() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHAKE_256S);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHAKE-256f key pair generator. */
+ public static final class wcKeyPairGenSlhDsaShake_256f
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaShake_256f object */
+ public wcKeyPairGenSlhDsaShake_256f() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHAKE_256F);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHA2-128s key pair generator. */
+ public static final class wcKeyPairGenSlhDsaSha2_128s
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaSha2_128s object */
+ public wcKeyPairGenSlhDsaSha2_128s() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHA2_128S);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHA2-128f key pair generator. */
+ public static final class wcKeyPairGenSlhDsaSha2_128f
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaSha2_128f object */
+ public wcKeyPairGenSlhDsaSha2_128f() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHA2_128F);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHA2-192s key pair generator. */
+ public static final class wcKeyPairGenSlhDsaSha2_192s
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaSha2_192s object */
+ public wcKeyPairGenSlhDsaSha2_192s() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHA2_192S);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHA2-192f key pair generator. */
+ public static final class wcKeyPairGenSlhDsaSha2_192f
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaSha2_192f object */
+ public wcKeyPairGenSlhDsaSha2_192f() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHA2_192F);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHA2-256s key pair generator. */
+ public static final class wcKeyPairGenSlhDsaSha2_256s
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaSha2_256s object */
+ public wcKeyPairGenSlhDsaSha2_256s() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHA2_256S);
+ }
+ }
+
+ /** wolfCrypt SLH-DSA-SHA2-256f key pair generator. */
+ public static final class wcKeyPairGenSlhDsaSha2_256f
+ extends WolfCryptKeyPairGenerator {
+ /** Create new wcKeyPairGenSlhDsaSha2_256f object */
+ public wcKeyPairGenSlhDsaSha2_256f() {
+ super(KeyType.WC_SLH_DSA, SlhDsa.SLH_DSA_SHA2_256F);
+ }
+ }
+
/**
* wolfCrypt ML-KEM (FIPS 203) generic key pair generator. Accepts any of
* the three ML-KEM parameter sets via
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsKeyFactory.java
index 57ee80ea..c49cc10e 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsKeyFactory.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsKeyFactory.java
@@ -100,6 +100,7 @@ protected T engineGetKeySpec(Key key, Class keySpec)
throws InvalidKeySpecException {
byte[] encoded;
+ Key wolfKey;
if (key == null) {
throw new InvalidKeySpecException("Key cannot be null");
@@ -110,14 +111,21 @@ protected T engineGetKeySpec(Key key, Class keySpec)
"Requested KeySpec class cannot be null");
}
- if (key instanceof PublicKey) {
+ /* Normalize key, validates foreign keys (encoding, type, DER) */
+ try {
+ wolfKey = engineTranslateKey(key);
+ } catch (InvalidKeyException e) {
+ throw new InvalidKeySpecException(e.getMessage(), e);
+ }
+
+ if (wolfKey instanceof PublicKey) {
if (!keySpec.isAssignableFrom(X509EncodedKeySpec.class)) {
throw new InvalidKeySpecException(
"LMS public keys can only be expressed as " +
"X509EncodedKeySpec, got request for: " +
keySpec.getName());
}
- encoded = requireEncoded(key, "X.509");
+ encoded = WolfCryptUtil.requireEncoded(wolfKey, "X.509");
return keySpec.cast(new X509EncodedKeySpec(encoded));
}
@@ -167,17 +175,4 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException {
"Unsupported Key type: " + key.getClass().getName());
}
- /* Return key.getEncoded(), throws if null/empty. */
- private static byte[] requireEncoded(Key key, String expectedFormat)
- throws InvalidKeySpecException {
-
- byte[] encoded = key.getEncoded();
-
- if (encoded == null || encoded.length == 0) {
- throw new InvalidKeySpecException(
- "Key has no encoded form (expected " + expectedFormat + ")");
- }
-
- return encoded;
- }
}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsPublicKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsPublicKey.java
index c232d45f..c4c0fdd5 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsPublicKey.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsPublicKey.java
@@ -91,7 +91,7 @@ public WolfCryptLmsPublicKey(byte[] x509Der)
/* Extract the raw HSS public key (accepts both SPKI body forms) and
* validate it by importing through wolfCrypt, which also derives the
* parameter set. */
- rawPub = WolfCryptLmsUtil.parsePublicKeyDer(x509Der);
+ rawPub = WolfCryptSpkiUtil.parseLmsPublicKeyDer(x509Der);
try {
key = new Lms();
@@ -118,7 +118,7 @@ public WolfCryptLmsPublicKey(byte[] x509Der)
/* Normalize to the canonical RFC 9708 unwrapped SubjectPublicKeyInfo,
* so getEncoded() and equals() are stable across both input
* encodings. */
- this.encoded = WolfCryptLmsUtil.encodePublicKeyDer(rawPub);
+ this.encoded = WolfCryptSpkiUtil.encodeLmsPublicKeyDer(rawPub);
this.rawPublicKey = rawPub;
}
@@ -287,7 +287,7 @@ private void readObject(ObjectInputStream in)
if (!destroyed && encoded != null) {
try {
this.rawPublicKey =
- WolfCryptLmsUtil.parsePublicKeyDer(encoded);
+ WolfCryptSpkiUtil.parseLmsPublicKeyDer(encoded);
}
catch (IllegalArgumentException e) {
throw (InvalidObjectException) new InvalidObjectException(
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsSignature.java
index 1f36ad47..bd7f7d07 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsSignature.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsSignature.java
@@ -111,7 +111,7 @@ private Lms importPublicKeyForVerify(PublicKey pub)
}
try {
- rawPub = WolfCryptLmsUtil.parsePublicKeyDer(der);
+ rawPub = WolfCryptSpkiUtil.parseLmsPublicKeyDer(der);
}
catch (IllegalArgumentException e) {
throw new InvalidKeyException(
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsUtil.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsUtil.java
deleted file mode 100644
index bdc06b86..00000000
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptLmsUtil.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/* WolfCryptLmsUtil.java
- *
- * Copyright (C) 2006-2026 wolfSSL Inc.
- *
- * This file is part of wolfSSL.
- *
- * wolfSSL 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 2 of the License, or
- * (at your option) any later version.
- *
- * wolfSSL 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
- */
-
-package com.wolfssl.provider.jce;
-
-import java.io.ByteArrayOutputStream;
-import java.util.Arrays;
-
-/**
- * ASN.1/DER helpers for LMS/HSS (RFC 8554) public keys.
- *
- * Native wolfCrypt provides a raw HSS/LMS public-key format
- * (wc_LmsKey_Export/ImportPubRaw()) and no SPKI decode, so the X.509
- * SubjectPublicKeyInfo wrapping is done here until native wolfSSL gets that
- * functionality. DER encoding reuses the shared
- * {@link WolfCryptASN1Util} helpers. DER decoding uses a small
- * self-contained strict reader. The LMS algorithm OID is
- * {@code id-alg-hss-lms-hashsig = 1.2.840.113549.1.9.16.3.17} (RFC 8708,
- * retained in RFC 9708).
- *
- * The DER reader here is intentionally self-contained and stricter than the
- * general-purpose {@link WolfCryptASN1Util}: it checks every tag and rejects
- * non-minimal long-form lengths, because it parses untrusted X.509
- * SubjectPublicKeyInfo input.
- *
- * Public key (SubjectPublicKeyInfo):
- *
- * SEQUENCE {
- * algorithm SEQUENCE { OBJECT IDENTIFIER } -- parameters absent or NULL
- * subjectPublicKey BIT STRING -- raw HSS/LMS public key
- * }
- *
- * The raw HSS/LMS public key has two encodings in the wild: RFC 8708 / JDK
- * <= 23 wrap it in an extra inner OCTET STRING inside the BIT STRING.
- * RFC 9708 / JDK 24+ / BouncyCastle place the raw bytes directly. wolfJCE
- * writes the unwrapped RFC 9708 form and reads both.
- *
- * wolfJCE provides verify-only LMS/HSS support, so there is no private-key
- * encoding here (matching the JDK SUN provider).
- */
-final class WolfCryptLmsUtil {
-
- /* DER tags used here. */
- private static final int TAG_BIT_STRING = 0x03;
- private static final int TAG_OCTET_STRING = 0x04;
- private static final int TAG_NULL = 0x05;
- private static final int TAG_OID = 0x06;
- private static final int TAG_SEQUENCE = 0x30;
-
- /* OID content bytes (no tag/length) for
- * 1.2.840.113549.1.9.16.3.17 (id-alg-hss-lms-hashsig). */
- private static final byte[] OID_HSS_LMS = {
- (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xF7,
- (byte)0x0D, (byte)0x01, (byte)0x09, (byte)0x10, (byte)0x03,
- (byte)0x11
- };
-
- /** Private constructor, all methods are static. */
- private WolfCryptLmsUtil() {
- }
-
- /**
- * Encode a raw HSS/LMS public key as an X.509 SubjectPublicKeyInfo DER,
- * RFC 9708 unwrapped form (raw key directly in the BIT STRING).
- *
- * @param rawPub raw HSS/LMS public key bytes
- *
- * @return X.509 SubjectPublicKeyInfo DER
- */
- static byte[] encodePublicKeyDer(byte[] rawPub) {
-
- byte[] algId;
- byte[] bitString;
-
- if (rawPub == null || rawPub.length == 0) {
- throw new IllegalArgumentException(
- "raw public key cannot be null or empty");
- }
-
- algId = WolfCryptASN1Util.encodeDERSequence(
- WolfCryptASN1Util.encodeDERObjectIdentifier(OID_HSS_LMS));
-
- /* RFC 9708 unwrapped form: the raw key sits directly in the BIT STRING
- * (encodeDERBitString prepends the 0x00 unused-bits octet). */
- bitString = WolfCryptASN1Util.encodeDERBitString(rawPub);
-
- return WolfCryptASN1Util.encodeDERSequence(concat(algId, bitString));
- }
-
- /**
- * Parse an X.509 SubjectPublicKeyInfo DER carrying an LMS/HSS public key
- * and return the raw HSS/LMS public key bytes. Accepts both the RFC 9708
- * unwrapped form and the RFC 8708 form (raw key wrapped in an inner OCTET
- * STRING).
- *
- * @param x509 X.509 SubjectPublicKeyInfo DER
- *
- * @return raw HSS/LMS public key bytes
- *
- * @throws IllegalArgumentException if the DER is malformed or not an LMS
- * SubjectPublicKeyInfo
- */
- static byte[] parsePublicKeyDer(byte[] x509) {
-
- int end;
- int bsStart;
- int bsEnd;
- int contentStart;
- int[] spki, algId, oid, bitStr, oct, nullParam;
-
- if (x509 == null || x509.length == 0) {
- throw new IllegalArgumentException(
- "encoded key cannot be null or empty");
- }
-
- /* outer SubjectPublicKeyInfo SEQUENCE, no trailing data */
- spki = readTLV(x509, 0, x509.length, TAG_SEQUENCE);
- if (spki[1] != x509.length) {
- throw bad("trailing data after SubjectPublicKeyInfo");
- }
- end = spki[1];
-
- /* AlgorithmIdentifier SEQUENCE { OID [, NULL] }. Parameters should be
- * absent (RFC 8708/9708), but JDK <= 17 re-encodes them as an explicit
- * NULL, so a single trailing NULL is also tolerated. */
- algId = readTLV(x509, spki[0], end, TAG_SEQUENCE);
- oid = readTLV(x509, algId[0], algId[1], TAG_OID);
- if (!regionEquals(x509, oid[0], oid[1], OID_HSS_LMS)) {
- throw bad("not an LMS/HSS AlgorithmIdentifier OID");
- }
- if (oid[1] != algId[1]) {
- /* The parameters must be a single NULL (05 00) */
- if ((x509[oid[1]] & 0xFF) != TAG_NULL) {
- throw bad("unexpected AlgorithmIdentifier parameters");
- }
- nullParam = readTLV(x509, oid[1], algId[1], TAG_NULL);
- if (nullParam[0] != nullParam[1] || nullParam[1] != algId[1]) {
- throw bad("unexpected AlgorithmIdentifier parameters");
- }
- }
-
- /* subjectPublicKey BIT STRING, no trailing data */
- bitStr = readTLV(x509, algId[1], end, TAG_BIT_STRING);
- if (bitStr[1] != end) {
- throw bad("trailing data after subjectPublicKey");
- }
- bsStart = bitStr[0];
- bsEnd = bitStr[1];
- if (bsStart >= bsEnd) {
- throw bad("empty subjectPublicKey BIT STRING");
- }
- if ((x509[bsStart] & 0xFF) != 0x00) {
- throw bad("unexpected unused bits in subjectPublicKey");
- }
- contentStart = bsStart + 1;
- if (contentStart >= bsEnd) {
- throw bad("missing public key in subjectPublicKey");
- }
-
- /* RFC 8708 wraps the raw key in an OCTET STRING (tag 0x04). The raw
- * HSS public key always begins with u32(L), L in 1..4, (ie byte
- * 0x00), so a leading 0x04 unambiguously means the wrapped form. */
- if ((x509[contentStart] & 0xFF) == TAG_OCTET_STRING) {
- oct = readTLV(x509, contentStart, bsEnd, TAG_OCTET_STRING);
- if (oct[1] != bsEnd) {
- throw bad("trailing data in wrapped public key");
- }
- return Arrays.copyOfRange(x509, oct[0], oct[1]);
- }
-
- return Arrays.copyOfRange(x509, contentStart, bsEnd);
- }
-
- /*
- * Parse a definite-form DER TLV at offset 'off' within [off, limit),
- * requiring the given tag. Returns {contentStart, contentEnd}. Rejects
- * indefinite/non-minimal lengths and overruns.
- */
- private static int[] readTLV(byte[] in, int off, int limit, int tag) {
-
- int lenByte, contentStart, length, numLenBytes;
-
- if (off + 2 > limit) {
- throw bad("truncated TLV header");
- }
-
- if ((in[off] & 0xFF) != tag) {
- throw bad("unexpected tag 0x" +
- Integer.toHexString(in[off] & 0xFF) + ", expected 0x" +
- Integer.toHexString(tag));
- }
-
- lenByte = in[off + 1] & 0xFF;
-
- if (lenByte < 0x80) {
- length = lenByte;
- contentStart = off + 2;
- }
- else {
- numLenBytes = lenByte & 0x7F;
-
- if (numLenBytes == 0 || numLenBytes > 4) {
- throw bad("unsupported DER length form");
- }
-
- if (off + 2 + numLenBytes > limit) {
- throw bad("truncated DER length");
- }
- length = 0;
-
- for (int i = 0; i < numLenBytes; i++) {
- length = (length << 8) | (in[off + 2 + i] & 0xFF);
- }
-
- /* Values < 0x80 must use the short form. n-octet length must
- * be n-octets long. */
- if (length < 0x80 ||
- length < (1 << ((numLenBytes - 1) * 8))) {
- throw bad("non-minimal DER length");
- }
-
- contentStart = off + 2 + numLenBytes;
- }
-
- /* contentStart <= limit was checked above, so limit - contentStart is
- * non-negative and a crafted ~2GB long-form length cannot overflow
- * the comparison. */
- if ((length < 0) || (length > limit - contentStart)) {
- throw bad("DER length exceeds buffer");
- }
-
- return new int[] { contentStart, contentStart + length };
- }
-
- /* Compare a region of 'in' against 'expected'. */
- private static boolean regionEquals(byte[] in, int start, int end,
- byte[] expected) {
-
- if ((end - start) != expected.length) {
- return false;
- }
-
- for (int i = 0; i < expected.length; i++) {
- if (in[start + i] != expected[i]) {
- return false;
- }
- }
-
- return true;
- }
-
- /* Concatenate byte arrays. */
- private static byte[] concat(byte[]... parts) {
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- for (byte[] p : parts) {
- out.write(p, 0, p.length);
- }
-
- return out.toByteArray();
- }
-
- private static IllegalArgumentException bad(String msg) {
- return new IllegalArgumentException("LMS DER parse error: " + msg);
- }
-}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaKeyFactory.java
index 407811fa..536518ef 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaKeyFactory.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaKeyFactory.java
@@ -194,6 +194,8 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec)
protected T engineGetKeySpec(Key key, Class keySpec)
throws InvalidKeySpecException {
+ Key wolfKey;
+
if (key == null) {
throw new InvalidKeySpecException("Key cannot be null");
}
@@ -202,34 +204,33 @@ protected T engineGetKeySpec(Key key, Class keySpec)
"Requested KeySpec class cannot be null");
}
- if (key instanceof WolfCryptMlDsaPrivateKey) {
- checkLevelMatchesRequired(
- ((WolfCryptMlDsaPrivateKey)key).getLevel());
- }
- else if (key instanceof WolfCryptMlDsaPublicKey) {
- checkLevelMatchesRequired(
- ((WolfCryptMlDsaPublicKey)key).getLevel());
+ /* Normalize key types, validates level on wolfJCE and
+ * encoding/DER/level on foreign keys. */
+ try {
+ wolfKey = engineTranslateKey(key);
+ } catch (InvalidKeyException e) {
+ throw new InvalidKeySpecException(e.getMessage(), e);
}
- if (key instanceof PrivateKey) {
+ if (wolfKey instanceof PrivateKey) {
if (!keySpec.isAssignableFrom(PKCS8EncodedKeySpec.class)) {
throw new InvalidKeySpecException(
"ML-DSA private keys can only be expressed as " +
"PKCS8EncodedKeySpec, got request for: " +
keySpec.getName());
}
- byte[] encoded = requireEncoded(key, "PKCS#8");
+ byte[] encoded = WolfCryptUtil.requireEncoded(wolfKey, "PKCS#8");
return keySpec.cast(new PKCS8EncodedKeySpec(encoded));
}
- if (key instanceof PublicKey) {
+ if (wolfKey instanceof PublicKey) {
if (!keySpec.isAssignableFrom(X509EncodedKeySpec.class)) {
throw new InvalidKeySpecException(
"ML-DSA public keys can only be expressed as " +
"X509EncodedKeySpec, got request for: " +
keySpec.getName());
}
- byte[] encoded = requireEncoded(key, "X.509");
+ byte[] encoded = WolfCryptUtil.requireEncoded(wolfKey, "X.509");
return keySpec.cast(new X509EncodedKeySpec(encoded));
}
@@ -316,15 +317,4 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException {
"Unsupported Key type: " + key.getClass().getName());
}
- /* Return key getEncoded(), throws if null/empty. */
- private static byte[] requireEncoded(Key key, String expectedFormat)
- throws InvalidKeySpecException {
-
- byte[] encoded = key.getEncoded();
- if (encoded == null || encoded.length == 0) {
- throw new InvalidKeySpecException(
- "Key has no encoded form (expected " + expectedFormat + ")");
- }
- return encoded;
- }
}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaSignature.java
index c4f72479..c7e013fe 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaSignature.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptMlDsaSignature.java
@@ -272,6 +272,7 @@ private MlDsa importKeyDer(byte[] der, int level, boolean isPublic,
/* Run the native public or private DER import on key object */
private static void importDer(MlDsa k, byte[] der, boolean isPublic) {
+
if (isPublic) {
k.importPublicKeyDer(der);
}
@@ -309,6 +310,7 @@ protected void engineInitVerify(PublicKey publicKey)
if (publicKey == null) {
throw new InvalidKeyException("PublicKey is null");
}
+
resetForInit();
this.key = importPublicKeyForVerify(publicKey);
this.signing = false;
@@ -321,6 +323,7 @@ protected void engineInitSign(PrivateKey privateKey)
if (privateKey == null) {
throw new InvalidKeyException("PrivateKey is null");
}
+
resetForInit();
this.key = importPrivateKeyForSign(privateKey);
this.signing = true;
@@ -328,9 +331,11 @@ protected void engineInitSign(PrivateKey privateKey)
@Override
protected void engineUpdate(byte b) throws SignatureException {
+
if (this.key == null) {
throw new SignatureException("Signature not initialized");
}
+
this.buffer.write(b);
}
@@ -341,18 +346,22 @@ protected void engineUpdate(byte[] b, int off, int len)
if (this.key == null) {
throw new SignatureException("Signature not initialized");
}
- if (b == null || off < 0 || len < 0 || off + len > b.length) {
+
+ if (b == null || off < 0 || len < 0 || len > b.length - off) {
throw new SignatureException("Invalid update arguments");
}
+
this.buffer.write(b, off, len);
}
@Override
protected byte[] engineSign() throws SignatureException {
+
if (this.key == null || !this.signing) {
throw new SignatureException(
"Signature not initialized for signing");
}
+
try {
byte[] msg = this.buffer.toByteArray();
byte[] sig = this.key.sign(msg, getOrInitRng());
@@ -377,6 +386,7 @@ protected boolean engineVerify(byte[] sigBytes)
if (sigBytes == null) {
throw new SignatureException("Signature bytes are null");
}
+
try {
byte[] msg = this.buffer.toByteArray();
boolean ok = this.key.verify(sigBytes, msg);
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java
index 4338a9fa..cacc22a1 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java
@@ -367,6 +367,169 @@ private void registerServices() {
put("Alg.Alias.Signature.OID.1.2.840.113549.1.9.16.3.17", "LMS");
}
+ /* SLH-DSA (FIPS 205) Signature support */
+ if (FeatureDetect.SlhDsaEnabled()) {
+ /* Generic alias accepts any of the SLH-DSA parameter sets */
+ put("Signature.SLH-DSA",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsa");
+
+ /* Per-parameter-set aliases */
+ put("Signature.SLH-DSA-SHA2-128s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaSha2_128s");
+ put("Signature.SLH-DSA-SHA2-128f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaSha2_128f");
+ put("Signature.SLH-DSA-SHA2-192s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaSha2_192s");
+ put("Signature.SLH-DSA-SHA2-192f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaSha2_192f");
+ put("Signature.SLH-DSA-SHA2-256s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaSha2_256s");
+ put("Signature.SLH-DSA-SHA2-256f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaSha2_256f");
+ put("Signature.SLH-DSA-SHAKE-128s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaShake_128s");
+ put("Signature.SLH-DSA-SHAKE-128f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaShake_128f");
+ put("Signature.SLH-DSA-SHAKE-192s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaShake_192s");
+ put("Signature.SLH-DSA-SHAKE-192f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaShake_192f");
+ put("Signature.SLH-DSA-SHAKE-256s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaShake_256s");
+ put("Signature.SLH-DSA-SHAKE-256f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcSlhDsaShake_256f");
+
+ /* OID aliases (FIPS 205: 2.16.840.1.101.3.4.3.20-.31) */
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.20",
+ "SLH-DSA-SHA2-128s");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.20",
+ "SLH-DSA-SHA2-128s");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.21",
+ "SLH-DSA-SHA2-128f");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.21",
+ "SLH-DSA-SHA2-128f");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.22",
+ "SLH-DSA-SHA2-192s");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.22",
+ "SLH-DSA-SHA2-192s");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.23",
+ "SLH-DSA-SHA2-192f");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.23",
+ "SLH-DSA-SHA2-192f");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.24",
+ "SLH-DSA-SHA2-256s");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.24",
+ "SLH-DSA-SHA2-256s");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.25",
+ "SLH-DSA-SHA2-256f");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.25",
+ "SLH-DSA-SHA2-256f");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.26",
+ "SLH-DSA-SHAKE-128s");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.26",
+ "SLH-DSA-SHAKE-128s");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.27",
+ "SLH-DSA-SHAKE-128f");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.27",
+ "SLH-DSA-SHAKE-128f");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.28",
+ "SLH-DSA-SHAKE-192s");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.28",
+ "SLH-DSA-SHAKE-192s");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.29",
+ "SLH-DSA-SHAKE-192f");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.29",
+ "SLH-DSA-SHAKE-192f");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.30",
+ "SLH-DSA-SHAKE-256s");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.30",
+ "SLH-DSA-SHAKE-256s");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.31",
+ "SLH-DSA-SHAKE-256f");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.31",
+ "SLH-DSA-SHAKE-256f");
+
+ /* HashSLH-DSA (pre-hash, FIPS 205 Section 10.2.2). Keys are
+ * regular SLH-DSA keys, only the signature algorithm differs,
+ * so only Signature services are registered. */
+ put("Signature.HASH-SLH-DSA",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsa");
+ put("Signature.SLH-DSA-SHA2-128s-WITH-SHA256",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaSha2_128sWithSha256");
+ put("Signature.SLH-DSA-SHA2-128f-WITH-SHA256",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaSha2_128fWithSha256");
+ put("Signature.SLH-DSA-SHA2-192s-WITH-SHA512",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaSha2_192sWithSha512");
+ put("Signature.SLH-DSA-SHA2-192f-WITH-SHA512",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaSha2_192fWithSha512");
+ put("Signature.SLH-DSA-SHA2-256s-WITH-SHA512",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaSha2_256sWithSha512");
+ put("Signature.SLH-DSA-SHA2-256f-WITH-SHA512",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaSha2_256fWithSha512");
+ put("Signature.SLH-DSA-SHAKE-128s-WITH-SHAKE128",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaShake_128sWithShake128");
+ put("Signature.SLH-DSA-SHAKE-128f-WITH-SHAKE128",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaShake_128fWithShake128");
+ put("Signature.SLH-DSA-SHAKE-192s-WITH-SHAKE256",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaShake_192sWithShake256");
+ put("Signature.SLH-DSA-SHAKE-192f-WITH-SHAKE256",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaShake_192fWithShake256");
+ put("Signature.SLH-DSA-SHAKE-256s-WITH-SHAKE256",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaShake_256sWithShake256");
+ put("Signature.SLH-DSA-SHAKE-256f-WITH-SHAKE256",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaSignature$wcHashSlhDsaShake_256fWithShake256");
+
+ /* HashSLH-DSA OID aliases (FIPS 205: 2.16.840.1.101.3.4.3.35-.46) */
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.35",
+ "SLH-DSA-SHA2-128s-WITH-SHA256");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.35",
+ "SLH-DSA-SHA2-128s-WITH-SHA256");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.36",
+ "SLH-DSA-SHA2-128f-WITH-SHA256");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.36",
+ "SLH-DSA-SHA2-128f-WITH-SHA256");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.37",
+ "SLH-DSA-SHA2-192s-WITH-SHA512");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.37",
+ "SLH-DSA-SHA2-192s-WITH-SHA512");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.38",
+ "SLH-DSA-SHA2-192f-WITH-SHA512");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.38",
+ "SLH-DSA-SHA2-192f-WITH-SHA512");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.39",
+ "SLH-DSA-SHA2-256s-WITH-SHA512");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.39",
+ "SLH-DSA-SHA2-256s-WITH-SHA512");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.40",
+ "SLH-DSA-SHA2-256f-WITH-SHA512");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.40",
+ "SLH-DSA-SHA2-256f-WITH-SHA512");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.41",
+ "SLH-DSA-SHAKE-128s-WITH-SHAKE128");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.41",
+ "SLH-DSA-SHAKE-128s-WITH-SHAKE128");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.42",
+ "SLH-DSA-SHAKE-128f-WITH-SHAKE128");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.42",
+ "SLH-DSA-SHAKE-128f-WITH-SHAKE128");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.43",
+ "SLH-DSA-SHAKE-192s-WITH-SHAKE256");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.43",
+ "SLH-DSA-SHAKE-192s-WITH-SHAKE256");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.44",
+ "SLH-DSA-SHAKE-192f-WITH-SHAKE256");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.44",
+ "SLH-DSA-SHAKE-192f-WITH-SHAKE256");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.45",
+ "SLH-DSA-SHAKE-256s-WITH-SHAKE256");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.45",
+ "SLH-DSA-SHAKE-256s-WITH-SHAKE256");
+ put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.46",
+ "SLH-DSA-SHAKE-256f-WITH-SHAKE256");
+ put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.46",
+ "SLH-DSA-SHAKE-256f-WITH-SHAKE256");
+ }
+
/* Mac */
if (FeatureDetect.HmacMd5Enabled()) {
put("Mac.HmacMD5",
@@ -668,6 +831,87 @@ private void registerServices() {
put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.19",
"ML-DSA-87");
}
+ if (FeatureDetect.SlhDsaKeyGenEnabled()) {
+ /* Generic alias: defaults to SLH-DSA-SHA2-128f, parameter set
+ * overridable via initialize(NamedParameterSpec). */
+ put("KeyPairGenerator.SLH-DSA",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsa");
+
+ put("KeyPairGenerator.SLH-DSA-SHA2-128s",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaSha2_128s");
+ put("KeyPairGenerator.SLH-DSA-SHA2-128f",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaSha2_128f");
+ put("KeyPairGenerator.SLH-DSA-SHA2-192s",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaSha2_192s");
+ put("KeyPairGenerator.SLH-DSA-SHA2-192f",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaSha2_192f");
+ put("KeyPairGenerator.SLH-DSA-SHA2-256s",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaSha2_256s");
+ put("KeyPairGenerator.SLH-DSA-SHA2-256f",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaSha2_256f");
+ put("KeyPairGenerator.SLH-DSA-SHAKE-128s",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaShake_128s");
+ put("KeyPairGenerator.SLH-DSA-SHAKE-128f",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaShake_128f");
+ put("KeyPairGenerator.SLH-DSA-SHAKE-192s",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaShake_192s");
+ put("KeyPairGenerator.SLH-DSA-SHAKE-192f",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaShake_192f");
+ put("KeyPairGenerator.SLH-DSA-SHAKE-256s",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaShake_256s");
+ put("KeyPairGenerator.SLH-DSA-SHAKE-256f",
+ "com.wolfssl.provider.jce.WolfCryptKeyPairGenerator$wcKeyPairGenSlhDsaShake_256f");
+
+ /* OID aliases (FIPS 205: 2.16.840.1.101.3.4.3.20-.31) */
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.20",
+ "SLH-DSA-SHA2-128s");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.20",
+ "SLH-DSA-SHA2-128s");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.21",
+ "SLH-DSA-SHA2-128f");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.21",
+ "SLH-DSA-SHA2-128f");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.22",
+ "SLH-DSA-SHA2-192s");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.22",
+ "SLH-DSA-SHA2-192s");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.23",
+ "SLH-DSA-SHA2-192f");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.23",
+ "SLH-DSA-SHA2-192f");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.24",
+ "SLH-DSA-SHA2-256s");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.24",
+ "SLH-DSA-SHA2-256s");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.25",
+ "SLH-DSA-SHA2-256f");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.25",
+ "SLH-DSA-SHA2-256f");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.26",
+ "SLH-DSA-SHAKE-128s");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.26",
+ "SLH-DSA-SHAKE-128s");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.27",
+ "SLH-DSA-SHAKE-128f");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.27",
+ "SLH-DSA-SHAKE-128f");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.28",
+ "SLH-DSA-SHAKE-192s");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.28",
+ "SLH-DSA-SHAKE-192s");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.29",
+ "SLH-DSA-SHAKE-192f");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.29",
+ "SLH-DSA-SHAKE-192f");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.30",
+ "SLH-DSA-SHAKE-256s");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.30",
+ "SLH-DSA-SHAKE-256s");
+ put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.31",
+ "SLH-DSA-SHAKE-256f");
+ put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.31",
+ "SLH-DSA-SHAKE-256f");
+ }
if (FeatureDetect.MlKemEnabled()) {
/* Generic alias: defaults to ML-KEM-768 */
put("KeyPairGenerator.ML-KEM",
@@ -793,6 +1037,85 @@ private void registerServices() {
put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.19",
"ML-DSA-87");
}
+ if (FeatureDetect.SlhDsaEnabled()) {
+ put("KeyFactory.SLH-DSA",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory");
+
+ put("KeyFactory.SLH-DSA-SHA2-128s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaSha2_128s");
+ put("KeyFactory.SLH-DSA-SHA2-128f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaSha2_128f");
+ put("KeyFactory.SLH-DSA-SHA2-192s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaSha2_192s");
+ put("KeyFactory.SLH-DSA-SHA2-192f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaSha2_192f");
+ put("KeyFactory.SLH-DSA-SHA2-256s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaSha2_256s");
+ put("KeyFactory.SLH-DSA-SHA2-256f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaSha2_256f");
+ put("KeyFactory.SLH-DSA-SHAKE-128s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaShake_128s");
+ put("KeyFactory.SLH-DSA-SHAKE-128f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaShake_128f");
+ put("KeyFactory.SLH-DSA-SHAKE-192s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaShake_192s");
+ put("KeyFactory.SLH-DSA-SHAKE-192f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaShake_192f");
+ put("KeyFactory.SLH-DSA-SHAKE-256s",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaShake_256s");
+ put("KeyFactory.SLH-DSA-SHAKE-256f",
+ "com.wolfssl.provider.jce.WolfCryptSlhDsaKeyFactory$wcSlhDsaShake_256f");
+
+ /* OID aliases (FIPS 205: 2.16.840.1.101.3.4.3.20-.31) */
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.20",
+ "SLH-DSA-SHA2-128s");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.20",
+ "SLH-DSA-SHA2-128s");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.21",
+ "SLH-DSA-SHA2-128f");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.21",
+ "SLH-DSA-SHA2-128f");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.22",
+ "SLH-DSA-SHA2-192s");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.22",
+ "SLH-DSA-SHA2-192s");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.23",
+ "SLH-DSA-SHA2-192f");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.23",
+ "SLH-DSA-SHA2-192f");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.24",
+ "SLH-DSA-SHA2-256s");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.24",
+ "SLH-DSA-SHA2-256s");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.25",
+ "SLH-DSA-SHA2-256f");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.25",
+ "SLH-DSA-SHA2-256f");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.26",
+ "SLH-DSA-SHAKE-128s");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.26",
+ "SLH-DSA-SHAKE-128s");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.27",
+ "SLH-DSA-SHAKE-128f");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.27",
+ "SLH-DSA-SHAKE-128f");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.28",
+ "SLH-DSA-SHAKE-192s");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.28",
+ "SLH-DSA-SHAKE-192s");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.29",
+ "SLH-DSA-SHAKE-192f");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.29",
+ "SLH-DSA-SHAKE-192f");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.30",
+ "SLH-DSA-SHAKE-256s");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.30",
+ "SLH-DSA-SHAKE-256s");
+ put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.31",
+ "SLH-DSA-SHAKE-256f");
+ put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.31",
+ "SLH-DSA-SHAKE-256f");
+ }
if (FeatureDetect.MlKemEnabled()) {
put("KeyFactory.ML-KEM",
"com.wolfssl.provider.jce.WolfCryptMlKemKeyFactory");
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaKeyFactory.java
new file mode 100644
index 00000000..88c7084a
--- /dev/null
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaKeyFactory.java
@@ -0,0 +1,416 @@
+/* WolfCryptSlhDsaKeyFactory.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce;
+
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.InvalidKeyException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import com.wolfssl.wolfcrypt.SlhDsa;
+
+/**
+ * wolfJCE SLH-DSA (FIPS 205) KeyFactory implementation.
+ *
+ * Converts between encoded forms ({@link PKCS8EncodedKeySpec} for private
+ * keys, {@link X509EncodedKeySpec} for public keys) and
+ * {@link WolfCryptSlhDsaPrivateKey} / {@link WolfCryptSlhDsaPublicKey}
+ * objects. The parameter set is carried in the encoded AlgorithmIdentifier
+ * OID (RFC 9909).
+ *
+ * The per-parameter-set factories (e.g. {@code SLH-DSA-SHA2-128f}) reject
+ * keys of a different parameter set. The generic {@code SLH-DSA} factory
+ * accepts keys of any parameter set.
+ */
+public class WolfCryptSlhDsaKeyFactory extends KeyFactorySpi {
+
+ /** No required parameter set, accept any. */
+ private static final int PARAM_ANY = -1;
+
+ /** Required SLH-DSA parameter set for keys produced by this factory, one
+ * of {@code SlhDsa.SLH_DSA_*} (0-11), or {@link #PARAM_ANY} to accept any
+ * parameter set. */
+ private final int requiredParam;
+
+ /**
+ * Create a new wolfJCE SLH-DSA KeyFactory accepting any parameter set.
+ */
+ public WolfCryptSlhDsaKeyFactory() {
+ this(PARAM_ANY);
+ }
+
+ /**
+ * Create a new wolfJCE SLH-DSA KeyFactory with a required parameter set.
+ *
+ * @param requiredParam {@link #PARAM_ANY} to accept any parameter set,
+ * otherwise one of {@code SlhDsa.SLH_DSA_*}
+ */
+ protected WolfCryptSlhDsaKeyFactory(int requiredParam) {
+
+ this.requiredParam = requiredParam;
+
+ log("created new SLH-DSA KeyFactory (requiredParam: " +
+ requiredParam + ")");
+ }
+
+ /** SLH-DSA-SHAKE-128s only KeyFactory. */
+ public static final class wcSlhDsaShake_128s
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaShake_128s() {
+ super(SlhDsa.SLH_DSA_SHAKE_128S);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-128f only KeyFactory. */
+ public static final class wcSlhDsaShake_128f
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaShake_128f() {
+ super(SlhDsa.SLH_DSA_SHAKE_128F);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-192s only KeyFactory. */
+ public static final class wcSlhDsaShake_192s
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaShake_192s() {
+ super(SlhDsa.SLH_DSA_SHAKE_192S);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-192f only KeyFactory. */
+ public static final class wcSlhDsaShake_192f
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaShake_192f() {
+ super(SlhDsa.SLH_DSA_SHAKE_192F);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-256s only KeyFactory. */
+ public static final class wcSlhDsaShake_256s
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaShake_256s() {
+ super(SlhDsa.SLH_DSA_SHAKE_256S);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-256f only KeyFactory. */
+ public static final class wcSlhDsaShake_256f
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaShake_256f() {
+ super(SlhDsa.SLH_DSA_SHAKE_256F);
+ }
+ }
+
+ /** SLH-DSA-SHA2-128s only KeyFactory. */
+ public static final class wcSlhDsaSha2_128s
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaSha2_128s() {
+ super(SlhDsa.SLH_DSA_SHA2_128S);
+ }
+ }
+
+ /** SLH-DSA-SHA2-128f only KeyFactory. */
+ public static final class wcSlhDsaSha2_128f
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaSha2_128f() {
+ super(SlhDsa.SLH_DSA_SHA2_128F);
+ }
+ }
+
+ /** SLH-DSA-SHA2-192s only KeyFactory. */
+ public static final class wcSlhDsaSha2_192s
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaSha2_192s() {
+ super(SlhDsa.SLH_DSA_SHA2_192S);
+ }
+ }
+
+ /** SLH-DSA-SHA2-192f only KeyFactory. */
+ public static final class wcSlhDsaSha2_192f
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaSha2_192f() {
+ super(SlhDsa.SLH_DSA_SHA2_192F);
+ }
+ }
+
+ /** SLH-DSA-SHA2-256s only KeyFactory. */
+ public static final class wcSlhDsaSha2_256s
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaSha2_256s() {
+ super(SlhDsa.SLH_DSA_SHA2_256S);
+ }
+ }
+
+ /** SLH-DSA-SHA2-256f only KeyFactory. */
+ public static final class wcSlhDsaSha2_256f
+ extends WolfCryptSlhDsaKeyFactory {
+ /** Default constructor. */
+ public wcSlhDsaSha2_256f() {
+ super(SlhDsa.SLH_DSA_SHA2_256F);
+ }
+ }
+
+ /**
+ * Check a key parameter set against this factory's required parameter set.
+ *
+ * @param param parameter set of the key being produced or inspected
+ *
+ * @throws InvalidKeySpecException if the factory is parameter-set specific
+ * and the key parameter set does not match
+ */
+ private void checkParamMatchesRequired(int param)
+ throws InvalidKeySpecException {
+
+ if ((requiredParam != PARAM_ANY) && (param != requiredParam)) {
+ throw new InvalidKeySpecException(
+ "Key parameter set does not match KeyFactory: expected " +
+ WolfPQCJdkCompat.slhDsaParamToName(requiredParam) +
+ ", got " + WolfPQCJdkCompat.slhDsaParamToName(param));
+ }
+ }
+
+ private void log(String msg) {
+ WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO,
+ () -> "[SLH-DSA KeyFactory] " + msg);
+ }
+
+ @Override
+ protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
+ throws InvalidKeySpecException {
+
+ byte[] encoded;
+
+ if (keySpec == null) {
+ throw new InvalidKeySpecException("KeySpec cannot be null");
+ }
+
+ if (!(keySpec instanceof PKCS8EncodedKeySpec)) {
+ throw new InvalidKeySpecException(
+ "Unsupported KeySpec type for SLH-DSA private key: " +
+ keySpec.getClass().getName() +
+ " (expected PKCS8EncodedKeySpec)");
+ }
+
+ encoded = ((PKCS8EncodedKeySpec) keySpec).getEncoded();
+ if (encoded == null || encoded.length == 0) {
+ throw new InvalidKeySpecException(
+ "PKCS8EncodedKeySpec is empty");
+ }
+
+ try {
+ WolfCryptSlhDsaPrivateKey key =
+ new WolfCryptSlhDsaPrivateKey(encoded);
+ checkParamMatchesRequired(key.getParam());
+ return key;
+ }
+ catch (IllegalArgumentException e) {
+ throw new InvalidKeySpecException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ protected PublicKey engineGeneratePublic(KeySpec keySpec)
+ throws InvalidKeySpecException {
+
+ byte[] encoded;
+
+ if (keySpec == null) {
+ throw new InvalidKeySpecException("KeySpec cannot be null");
+ }
+
+ if (!(keySpec instanceof X509EncodedKeySpec)) {
+ throw new InvalidKeySpecException(
+ "Unsupported KeySpec type for SLH-DSA public key: " +
+ keySpec.getClass().getName() +
+ " (expected X509EncodedKeySpec)");
+ }
+
+ encoded = ((X509EncodedKeySpec) keySpec).getEncoded();
+ if (encoded == null || encoded.length == 0) {
+ throw new InvalidKeySpecException(
+ "X509EncodedKeySpec is empty");
+ }
+
+ try {
+ WolfCryptSlhDsaPublicKey key =
+ new WolfCryptSlhDsaPublicKey(encoded);
+ checkParamMatchesRequired(key.getParam());
+ return key;
+ }
+ catch (IllegalArgumentException e) {
+ throw new InvalidKeySpecException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ protected T engineGetKeySpec(Key key, Class keySpec)
+ throws InvalidKeySpecException {
+
+ byte[] encoded;
+ Key wolfKey;
+
+ if (key == null) {
+ throw new InvalidKeySpecException("Key cannot be null");
+ }
+
+ if (keySpec == null) {
+ throw new InvalidKeySpecException(
+ "Requested KeySpec class cannot be null");
+ }
+
+ /* Normalize key types, validates parameter set on wolfJCE keys and
+ * encoding/DER/parameter set on foreign keys. */
+ try {
+ wolfKey = engineTranslateKey(key);
+ } catch (InvalidKeyException e) {
+ throw new InvalidKeySpecException(e.getMessage(), e);
+ }
+
+ if (wolfKey instanceof PrivateKey) {
+ if (!keySpec.isAssignableFrom(PKCS8EncodedKeySpec.class)) {
+ throw new InvalidKeySpecException(
+ "SLH-DSA private keys can only be expressed as " +
+ "PKCS8EncodedKeySpec, got request for: " +
+ keySpec.getName());
+ }
+ encoded = WolfCryptUtil.requireEncoded(wolfKey, "PKCS#8");
+ return keySpec.cast(new PKCS8EncodedKeySpec(encoded));
+ }
+
+ if (wolfKey instanceof PublicKey) {
+ if (!keySpec.isAssignableFrom(X509EncodedKeySpec.class)) {
+ throw new InvalidKeySpecException(
+ "SLH-DSA public keys can only be expressed as " +
+ "X509EncodedKeySpec, got request for: " +
+ keySpec.getName());
+ }
+ encoded = WolfCryptUtil.requireEncoded(wolfKey, "X.509");
+ return keySpec.cast(new X509EncodedKeySpec(encoded));
+ }
+
+ throw new InvalidKeySpecException(
+ "Unsupported Key type: " + key.getClass().getName());
+ }
+
+ @Override
+ protected Key engineTranslateKey(Key key) throws InvalidKeyException {
+
+ byte[] encoded;
+
+ if (key == null) {
+ throw new InvalidKeyException("Key cannot be null");
+ }
+
+ try {
+ /* Already one of ours, no translation needed but parameter set is
+ * still checked for parameter-set specific factories. */
+ if (key instanceof WolfCryptSlhDsaPrivateKey) {
+ checkParamMatchesRequired(
+ ((WolfCryptSlhDsaPrivateKey)key).getParam());
+ return key;
+ }
+
+ if (key instanceof WolfCryptSlhDsaPublicKey) {
+ checkParamMatchesRequired(
+ ((WolfCryptSlhDsaPublicKey)key).getParam());
+ return key;
+ }
+
+ if (key instanceof PrivateKey) {
+ String fmt = key.getFormat();
+ if (!"PKCS#8".equalsIgnoreCase(fmt)) {
+ throw new InvalidKeyException(
+ "Cannot translate SLH-DSA private key with format: " +
+ fmt + " (expected PKCS#8)");
+ }
+
+ encoded = key.getEncoded();
+ if (encoded == null || encoded.length == 0) {
+ throw new InvalidKeyException(
+ "Source private key has no PKCS#8 encoding");
+ }
+
+ try {
+ WolfCryptSlhDsaPrivateKey translated =
+ new WolfCryptSlhDsaPrivateKey(encoded);
+ checkParamMatchesRequired(translated.getParam());
+ return translated;
+ }
+ catch (IllegalArgumentException e) {
+ throw new InvalidKeyException(
+ "Source key is not a valid SLH-DSA PKCS#8 key: " +
+ e.getMessage(), e);
+ }
+ }
+
+ if (key instanceof PublicKey) {
+ String fmt = key.getFormat();
+ if (!"X.509".equalsIgnoreCase(fmt)) {
+ throw new InvalidKeyException(
+ "Cannot translate SLH-DSA public key with format: " +
+ fmt + " (expected X.509)");
+ }
+
+ encoded = key.getEncoded();
+ if (encoded == null || encoded.length == 0) {
+ throw new InvalidKeyException(
+ "Source public key has no X.509 encoding");
+ }
+
+ try {
+ WolfCryptSlhDsaPublicKey translated =
+ new WolfCryptSlhDsaPublicKey(encoded);
+ checkParamMatchesRequired(translated.getParam());
+ return translated;
+ }
+ catch (IllegalArgumentException e) {
+ throw new InvalidKeyException(
+ "Source key is not a valid SLH-DSA X.509 SPKI: " +
+ e.getMessage(), e);
+ }
+ }
+ }
+ catch (InvalidKeySpecException e) {
+ throw new InvalidKeyException(e.getMessage(), e);
+ }
+
+ throw new InvalidKeyException(
+ "Unsupported Key type: " + key.getClass().getName());
+ }
+
+}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPrivateKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPrivateKey.java
new file mode 100644
index 00000000..093d262e
--- /dev/null
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPrivateKey.java
@@ -0,0 +1,333 @@
+/* WolfCryptSlhDsaPrivateKey.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.Arrays;
+import java.security.PrivateKey;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.security.auth.Destroyable;
+
+import com.wolfssl.wolfcrypt.SlhDsa;
+import com.wolfssl.wolfcrypt.WolfCryptError;
+import com.wolfssl.wolfcrypt.WolfCryptException;
+
+/**
+ * wolfJCE SLH-DSA (FIPS 205) private key.
+ *
+ * {@link #getAlgorithm()} returns the family name {@code "SLH-DSA"}. The
+ * parameter set is exposed via {@link #getParams()}.
+ *
+ * {@link #getEncoded()} returns PKCS#8 PrivateKeyInfo DER (RFC 9909)
+ * generated via wolfCrypt {@code wc_SlhDsaKey_KeyToDer}. The encoded buffer
+ * is zeroed on {@link #destroy()}.
+ */
+public class WolfCryptSlhDsaPrivateKey implements PrivateKey, Destroyable {
+
+ private static final long serialVersionUID = 1L;
+
+ /** PKCS#8 PrivateKeyInfo DER. Zeroed on destroy(). */
+ private byte[] encoded = null;
+
+ /** SLH-DSA parameter set, one of {@code SlhDsa.SLH_DSA_*} (0-11). */
+ private final int param;
+
+ /** Track if object has been destroyed. */
+ private boolean destroyed = false;
+
+ /** Lock around use of destroyed boolean and encoded buffer. */
+ private transient Object stateLock = new Object();
+
+ /**
+ * Create from PKCS#8 PrivateKeyInfo DER. The parameter set is detected
+ * from the AlgorithmIdentifier OID by wolfCrypt.
+ *
+ * @param pkcs8Der PKCS#8 PrivateKeyInfo DER
+ *
+ * @throws IllegalArgumentException if DER is malformed, not a
+ * recognized SLH-DSA PKCS#8, or private-key support for it is
+ * not compiled into native wolfSSL
+ */
+ public WolfCryptSlhDsaPrivateKey(byte[] pkcs8Der)
+ throws IllegalArgumentException {
+
+ if (pkcs8Der == null || pkcs8Der.length == 0) {
+ throw new IllegalArgumentException(
+ "Encoded key data cannot be null or empty");
+ }
+
+ try {
+ /* Parse PKCS#8 DER to verify correctness and detect param set */
+ this.param = SlhDsa.parseAndValidateSlhDsaPrivateKeyDer(pkcs8Der);
+ }
+ catch (WolfCryptException e) {
+ throw decodeFailure(e);
+ }
+
+ this.encoded = pkcs8Der.clone();
+ }
+
+ /**
+ * Create from PKCS#8 PrivateKeyInfo DER with explicit parameter set. The
+ * DER is validated by importing through wolfCrypt at construction and the
+ * detected parameter set must match {@code param}.
+ *
+ * @param pkcs8Der PKCS#8 PrivateKeyInfo DER
+ * @param param SLH-DSA parameter set, one of {@code SlhDsa.SLH_DSA_*}
+ *
+ * @throws IllegalArgumentException on invalid param, malformed DER, a
+ * parameter set mismatch, or private-key support not compiled
+ * into native wolfSSL
+ */
+ public WolfCryptSlhDsaPrivateKey(byte[] pkcs8Der, int param)
+ throws IllegalArgumentException {
+
+ int detected;
+
+ validateParam(param);
+
+ if (pkcs8Der == null || pkcs8Der.length == 0) {
+ throw new IllegalArgumentException(
+ "Encoded key data cannot be null or empty");
+ }
+
+ try {
+ detected = SlhDsa.parseAndValidateSlhDsaPrivateKeyDer(pkcs8Der);
+ }
+ catch (WolfCryptException e) {
+ throw decodeFailure(e);
+ }
+
+ if (detected != param) {
+ throw new IllegalArgumentException(
+ "SLH-DSA PKCS#8 parameter set " +
+ WolfPQCJdkCompat.slhDsaParamToName(detected) +
+ " does not match requested " +
+ WolfPQCJdkCompat.slhDsaParamToName(param));
+ }
+
+ this.param = param;
+ this.encoded = pkcs8Der.clone();
+ }
+
+ /**
+ * Map a PKCS#8 decode failure to an IllegalArgumentException.
+ *
+ * @param e decode exception from parseAndValidateSlhDsaPrivateKeyDer
+ *
+ * @return IllegalArgumentException for the caller to throw
+ */
+ private static IllegalArgumentException decodeFailure(
+ WolfCryptException e) {
+
+ if (e.getError() == WolfCryptError.NOT_COMPILED_IN) {
+ return new IllegalArgumentException(
+ "SLH-DSA private-key support or this parameter set is " +
+ "not compiled into native wolfSSL", e);
+ }
+
+ return new IllegalArgumentException(
+ "Not a valid SLH-DSA PKCS#8 DER: " + e.getMessage(), e);
+ }
+
+ /**
+ * Validates SLH-DSA parameter set.
+ *
+ * @param param SLH-DSA parameter set to validate
+ *
+ * @throws IllegalArgumentException if param is not in range 0-11
+ */
+ private static void validateParam(int param)
+ throws IllegalArgumentException {
+
+ if (param < SlhDsa.SLH_DSA_SHAKE_128S ||
+ param > SlhDsa.SLH_DSA_SHA2_256F) {
+
+ throw new IllegalArgumentException(
+ "Invalid SLH-DSA parameter set: " + param);
+ }
+ }
+
+ /**
+ * Get the standard algorithm name for this key.
+ *
+ * @return "SLH-DSA"
+ */
+ @Override
+ public String getAlgorithm() {
+ return "SLH-DSA";
+ }
+
+ /**
+ * Get the name of the primary encoding format for this key.
+ *
+ * @return "PKCS#8"
+ */
+ @Override
+ public String getFormat() {
+ return "PKCS#8";
+ }
+
+ /**
+ * Get the PKCS#8 DER encoding of this key.
+ *
+ * @return PKCS#8 DER byte array, or null if the key has been destroyed
+ */
+ @Override
+ public byte[] getEncoded() {
+ synchronized (stateLock) {
+ if (destroyed) {
+ return null;
+ }
+ return encoded.clone();
+ }
+ }
+
+ /**
+ * Get the AlgorithmParameterSpec for this key.
+ *
+ * @return JDK {@code NamedParameterSpec} on JDK 11+, else null
+ */
+ public AlgorithmParameterSpec getParams() {
+ return WolfPQCJdkCompat.slhDsaNamedParameterSpec(this.param);
+ }
+
+ /**
+ * Get the SLH-DSA parameter set.
+ *
+ * @return parameter set value, one of {@code SlhDsa.SLH_DSA_*} (0-11)
+ */
+ public int getParam() {
+ return this.param;
+ }
+
+ /**
+ * Destroy this key, zero the encoded buffer and mark destroyed.
+ */
+ @Override
+ public void destroy() {
+ synchronized (stateLock) {
+ if (!destroyed) {
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte) 0);
+ }
+ destroyed = true;
+ }
+ }
+ }
+
+ /**
+ * Check if this key has been destroyed.
+ *
+ * @return true if destroyed, false otherwise
+ */
+ @Override
+ public boolean isDestroyed() {
+ synchronized (stateLock) {
+ return destroyed;
+ }
+ }
+
+ /**
+ * Hash code based on encoded key and parameter set.
+ *
+ * @return hash code, or 0 if destroyed
+ */
+ @Override
+ public int hashCode() {
+ synchronized (stateLock) {
+ if (destroyed) {
+ return 0;
+ }
+ return Arrays.hashCode(encoded) ^ param;
+ }
+ }
+
+ /**
+ * Equality based on encoded key and parameter set.
+ *
+ * @return true if obj is a PrivateKey with algorithm "SLH-DSA" and the
+ * same encoded key, false otherwise or if destroyed.
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ PrivateKey other;
+ byte[] otherEncoded;
+ String otherAlg;
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof PrivateKey)) {
+ return false;
+ }
+
+ other = (PrivateKey) obj;
+ otherEncoded = other.getEncoded();
+ otherAlg = other.getAlgorithm();
+
+ synchronized (stateLock) {
+ if (destroyed) {
+ return false;
+ }
+ return otherEncoded != null &&
+ "SLH-DSA".equals(otherAlg) &&
+ Arrays.equals(this.encoded, otherEncoded);
+ }
+ }
+
+ /**
+ * String representation of this key.
+ *
+ * @return string describing this key, or indicating destroyed state
+ */
+ @Override
+ public String toString() {
+ synchronized (stateLock) {
+ if (destroyed) {
+ return "WolfCryptSlhDsaPrivateKey[DESTROYED]";
+ }
+ return "WolfCryptSlhDsaPrivateKey[algorithm=SLH-DSA, " +
+ "params=" + WolfPQCJdkCompat.slhDsaParamToName(param) +
+ ", format=PKCS#8]";
+ }
+ }
+
+ /**
+ * Custom deserialization to reinitialize transient state after
+ * deserialization.
+ *
+ * @param in ObjectInputStream to read from
+ *
+ * @throws IOException if an I/O error occurs
+ * @throws ClassNotFoundException if a class cannot be found
+ */
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+
+ in.defaultReadObject();
+ stateLock = new Object();
+ }
+}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPublicKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPublicKey.java
new file mode 100644
index 00000000..b6a50e28
--- /dev/null
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaPublicKey.java
@@ -0,0 +1,339 @@
+/* WolfCryptSlhDsaPublicKey.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.Arrays;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.security.auth.Destroyable;
+
+import com.wolfssl.wolfcrypt.SlhDsa;
+import com.wolfssl.wolfcrypt.WolfCryptError;
+import com.wolfssl.wolfcrypt.WolfCryptException;
+
+/**
+ * wolfJCE SLH-DSA (FIPS 205) public key.
+ *
+ * {@link #getAlgorithm()} returns the family name {@code "SLH-DSA"}. The
+ * parameter set is exposed via {@link #getParams()}.
+ *
+ * {@link #getEncoded()} returns the X.509 SubjectPublicKeyInfo DER
+ * (RFC 9909) generated via wolfCrypt {@code wc_SlhDsaKey_PublicKeyToDer}.
+ */
+public class WolfCryptSlhDsaPublicKey implements PublicKey, Destroyable {
+
+ private static final long serialVersionUID = 1L;
+
+ /** X.509 SubjectPublicKeyInfo DER. */
+ private byte[] encoded = null;
+
+ /** SLH-DSA parameter set, one of {@code SlhDsa.SLH_DSA_*} (0-11). */
+ private final int param;
+
+ /** Track if object has been destroyed. */
+ private boolean destroyed = false;
+
+ /** Lock around use of destroyed boolean and encoded buffer. Cannot be
+ * final because it is reinitialized after deserialization. */
+ private transient Object stateLock = new Object();
+
+ /**
+ * Create from X.509 SubjectPublicKeyInfo DER. The parameter set is
+ * detected from the AlgorithmIdentifier OID by wolfCrypt.
+ *
+ * @param x509Der X.509 SubjectPublicKeyInfo DER
+ *
+ * @throws IllegalArgumentException if DER is malformed, not a
+ * recognized SLH-DSA SPKI, or its parameter set is not
+ * compiled into native wolfSSL
+ */
+ public WolfCryptSlhDsaPublicKey(byte[] x509Der)
+ throws IllegalArgumentException {
+
+ if (x509Der == null || x509Der.length == 0) {
+ throw new IllegalArgumentException(
+ "Encoded key data cannot be null or empty");
+ }
+
+ /* Remove NULL AlgorithmIdentifier (JDK re-encoding) if present */
+ x509Der = WolfCryptSpkiUtil.stripNullAlgIdParams(x509Der);
+
+ try {
+ this.param = SlhDsa.parseAndValidateSlhDsaPublicKeyDer(x509Der);
+ }
+ catch (WolfCryptException e) {
+ throw decodeFailure(e);
+ }
+
+ this.encoded = x509Der.clone();
+ }
+
+ /**
+ * Create from X.509 SubjectPublicKeyInfo DER with explicit parameter set.
+ * The DER is validated by importing through wolfCrypt at construction and
+ * the detected parameter set must match {@code param}.
+ *
+ * @param x509Der X.509 SubjectPublicKeyInfo DER
+ * @param param SLH-DSA parameter set, one of {@code SlhDsa.SLH_DSA_*}
+ *
+ * @throws IllegalArgumentException if param is invalid, DER is malformed,
+ * its parameter set does not match {@code param}, or its
+ * parameter set is not compiled into native wolfSSL.
+ */
+ public WolfCryptSlhDsaPublicKey(byte[] x509Der, int param)
+ throws IllegalArgumentException {
+
+ int detected;
+
+ validateParam(param);
+
+ if (x509Der == null || x509Der.length == 0) {
+ throw new IllegalArgumentException(
+ "Encoded key data cannot be null or empty");
+ }
+
+ /* Remove NULL AlgorithmIdentifier (JDK re-encoding) if present */
+ x509Der = WolfCryptSpkiUtil.stripNullAlgIdParams(x509Der);
+
+ try {
+ detected = SlhDsa.parseAndValidateSlhDsaPublicKeyDer(x509Der);
+ }
+ catch (WolfCryptException e) {
+ throw decodeFailure(e);
+ }
+
+ if (detected != param) {
+ throw new IllegalArgumentException(
+ "SLH-DSA SPKI parameter set " +
+ WolfPQCJdkCompat.slhDsaParamToName(detected) +
+ " does not match requested " +
+ WolfPQCJdkCompat.slhDsaParamToName(param));
+ }
+
+ this.param = param;
+ this.encoded = x509Der.clone();
+ }
+
+ /**
+ * Map an SPKI decode failure to an IllegalArgumentException.
+ *
+ * @param e decode exception from parseAndValidateSlhDsaPublicKeyDer
+ *
+ * @return IllegalArgumentException for the caller to throw
+ */
+ private static IllegalArgumentException decodeFailure(
+ WolfCryptException e) {
+
+ if (e.getError() == WolfCryptError.NOT_COMPILED_IN) {
+ return new IllegalArgumentException(
+ "SLH-DSA parameter set is not compiled into native wolfSSL", e);
+ }
+
+ return new IllegalArgumentException(
+ "Not a valid SLH-DSA X.509 SPKI DER: " + e.getMessage(), e);
+ }
+
+ /**
+ * Validates SLH-DSA parameter set.
+ *
+ * @param param SLH-DSA parameter set to validate
+ *
+ * @throws IllegalArgumentException if param is not in range 0-11
+ */
+ private static void validateParam(int param)
+ throws IllegalArgumentException {
+
+ if (param < SlhDsa.SLH_DSA_SHAKE_128S ||
+ param > SlhDsa.SLH_DSA_SHA2_256F) {
+
+ throw new IllegalArgumentException(
+ "Invalid SLH-DSA parameter set: " + param);
+ }
+ }
+
+ /**
+ * Get the standard algorithm name for this key.
+ *
+ * @return "SLH-DSA"
+ */
+ @Override
+ public String getAlgorithm() {
+ return "SLH-DSA";
+ }
+
+ /**
+ * Get the name of the primary encoding format for this key.
+ *
+ * @return "X.509"
+ */
+ @Override
+ public String getFormat() {
+ return "X.509";
+ }
+
+ /**
+ * Get the X.509 SubjectPublicKeyInfo DER encoding of this key.
+ *
+ * @return X.509 DER, or null if the key has been destroyed
+ */
+ @Override
+ public byte[] getEncoded() {
+ synchronized (stateLock) {
+ if (destroyed) {
+ return null;
+ }
+ return encoded.clone();
+ }
+ }
+
+ /**
+ * Get the AlgorithmParameterSpec for this key.
+ *
+ * @return JDK {@code NamedParameterSpec} on JDK 11+, else null
+ */
+ public AlgorithmParameterSpec getParams() {
+ return WolfPQCJdkCompat.slhDsaNamedParameterSpec(this.param);
+ }
+
+ /**
+ * Get the SLH-DSA parameter set.
+ *
+ * @return parameter set value, one of {@code SlhDsa.SLH_DSA_*} (0-11)
+ */
+ public int getParam() {
+ return this.param;
+ }
+
+ /**
+ * Destroy this key, zero the encoded buffer and mark destroyed.
+ */
+ @Override
+ public void destroy() {
+ synchronized (stateLock) {
+ if (!destroyed) {
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte) 0);
+ }
+ destroyed = true;
+ }
+ }
+ }
+
+ /**
+ * Check if this key has been destroyed.
+ *
+ * @return true if destroyed, false otherwise
+ */
+ @Override
+ public boolean isDestroyed() {
+ synchronized (stateLock) {
+ return destroyed;
+ }
+ }
+
+ /**
+ * Hash code based on encoded key and parameter set.
+ *
+ * @return hash code, or 0 if destroyed
+ */
+ @Override
+ public int hashCode() {
+ synchronized (stateLock) {
+ if (destroyed) {
+ return 0;
+ }
+ return Arrays.hashCode(encoded) ^ param;
+ }
+ }
+
+ /**
+ * Equality based on algorithm and encoded key. The SLH-DSA parameter
+ * set is carried in the X.509 SPKI AlgorithmIdentifier OID, so byte
+ * equality of the encoded form also implies the same parameter set.
+ *
+ * @return true if obj is a PublicKey with algorithm "SLH-DSA" and the
+ * same X.509 SPKI encoding, false otherwise or if destroyed.
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ PublicKey other;
+ byte[] otherEncoded;
+ String otherAlg;
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof PublicKey)) {
+ return false;
+ }
+
+ other = (PublicKey) obj;
+ otherEncoded = other.getEncoded();
+ otherAlg = other.getAlgorithm();
+
+ synchronized (stateLock) {
+ if (destroyed) {
+ return false;
+ }
+ return otherEncoded != null &&
+ "SLH-DSA".equals(otherAlg) &&
+ Arrays.equals(this.encoded, otherEncoded);
+ }
+ }
+
+ /**
+ * String representation of this key.
+ *
+ * @return string describing this key, or indicating it is destroyed
+ */
+ @Override
+ public String toString() {
+ synchronized (stateLock) {
+ if (destroyed) {
+ return "WolfCryptSlhDsaPublicKey[DESTROYED]";
+ }
+ return "WolfCryptSlhDsaPublicKey[algorithm=SLH-DSA, " +
+ "params=" + WolfPQCJdkCompat.slhDsaParamToName(param) +
+ ", format=X.509, encoded.length=" + encoded.length + "]";
+ }
+ }
+
+ /**
+ * Custom deserialization to reinitialize transient state after
+ * deserialization.
+ *
+ * @param in ObjectInputStream to read from
+ *
+ * @throws IOException if an I/O error occurs
+ * @throws ClassNotFoundException if a class cannot be found
+ */
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+
+ in.defaultReadObject();
+ stateLock = new Object();
+ }
+}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaSignature.java
new file mode 100644
index 00000000..89168123
--- /dev/null
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSlhDsaSignature.java
@@ -0,0 +1,685 @@
+/* WolfCryptSlhDsaSignature.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.SignatureSpi;
+import java.security.spec.AlgorithmParameterSpec;
+
+import com.wolfssl.wolfcrypt.SlhDsa;
+import com.wolfssl.wolfcrypt.Rng;
+import com.wolfssl.wolfcrypt.WolfCryptException;
+
+/**
+ * wolfJCE SLH-DSA (FIPS 205) signature provider.
+ *
+ * SLH-DSA is a one-shot signature scheme. This implementation buffers the
+ * message in a {@link ByteArrayOutputStream} during {@code engineUpdate} and
+ * consumes it on {@code engineSign} / {@code engineVerify}. This applies to
+ * the HashSLH-DSA (pre-hash) services as well: the full message is buffered
+ * and pre-hashed natively in one shot, so peak memory is proportional to
+ * message size. Incremental pre-hashing during update is a possible future
+ * enhancement for large-message use cases.
+ *
+ * An optional FIPS 205 context string (0..255 bytes, empty by default) can
+ * be set via {@code engineSetParameter(AlgorithmParameterSpec)} with a
+ * {@link WolfCryptContextParameterSpec}. The context persists across init
+ * calls until replaced. To reset, set a spec created with a null or empty
+ * context. The signer and verifier must agree on the same context otherwise
+ * verification fails.
+ *
+ * Inner classes restrict the accepted key parameter set; the generic
+ * {@link wcSlhDsa} accepts any SLH-DSA parameter set.
+ */
+public abstract class WolfCryptSlhDsaSignature extends SignatureSpi {
+
+ /** No required parameter set, accept any. */
+ private static final int PARAM_ANY = -1;
+
+ /** Required SLH-DSA parameter set for this Signature instance, one of
+ * {@code SlhDsa.SLH_DSA_*} (0-11), or {@link #PARAM_ANY} for the generic
+ * implementation. */
+ private final int requiredParam;
+
+ /** True for the HashSLH-DSA (pre-hash) services, false for pure SLH-DSA.
+ * When true the message is pre-hashed natively per FIPS 205 Section
+ * 10.2.2 before signing/verifying. */
+ private final boolean preHash;
+
+ /** Native SLH-DSA wrapper (one per init). */
+ private SlhDsa key = null;
+
+ /** Set true when initialized for sign, false for verify. Undefined
+ * before first init call. */
+ private boolean signing = false;
+
+ /** FIPS 205 context string. Empty by default. Persists across init so it
+ * may be set via setParameter() before or after initSign/initVerify. */
+ private byte[] context = new byte[0];
+
+ /** Reset keeps the backing array up to this size, larger buffers are
+ * reallocated so long-lived (cached/pooled) Signature objects do not pin
+ * one large message's worth of memory indefinitely. */
+ private static final int BUFFER_RETAIN_MAX = 1024 * 1024;
+
+ /** Buffered message bytes (SLH-DSA signs/verifies the whole message, not
+ * a streaming hash). */
+ private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ /** Lazily-initialized RNG used for sign operations. */
+ private Rng rng = null;
+
+ /**
+ * Construct with the required SLH-DSA parameter set. {@link #PARAM_ANY}
+ * means "any parameter set", used by the generic {@link wcSlhDsa} alias.
+ *
+ * @param requiredParam {@link #PARAM_ANY} or a {@code SlhDsa.SLH_DSA_*}
+ */
+ protected WolfCryptSlhDsaSignature(int requiredParam) {
+ this(requiredParam, false);
+ }
+
+ /**
+ * Construct with the required SLH-DSA parameter set and pre-hash mode.
+ *
+ * @param requiredParam {@link #PARAM_ANY} or a {@code SlhDsa.SLH_DSA_*}
+ * @param preHash true for HashSLH-DSA (pre-hash), false for pure SLH-DSA
+ */
+ protected WolfCryptSlhDsaSignature(int requiredParam, boolean preHash) {
+ this.requiredParam = requiredParam;
+ this.preHash = preHash;
+ }
+
+ /** Generic SLH-DSA Signature, accepts keys of any parameter set. */
+ public static final class wcSlhDsa extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsa() {
+ super(PARAM_ANY);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-128s only. */
+ public static final class wcSlhDsaShake_128s
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaShake_128s() {
+ super(SlhDsa.SLH_DSA_SHAKE_128S);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-128f only. */
+ public static final class wcSlhDsaShake_128f
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaShake_128f() {
+ super(SlhDsa.SLH_DSA_SHAKE_128F);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-192s only. */
+ public static final class wcSlhDsaShake_192s
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaShake_192s() {
+ super(SlhDsa.SLH_DSA_SHAKE_192S);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-192f only. */
+ public static final class wcSlhDsaShake_192f
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaShake_192f() {
+ super(SlhDsa.SLH_DSA_SHAKE_192F);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-256s only. */
+ public static final class wcSlhDsaShake_256s
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaShake_256s() {
+ super(SlhDsa.SLH_DSA_SHAKE_256S);
+ }
+ }
+
+ /** SLH-DSA-SHAKE-256f only. */
+ public static final class wcSlhDsaShake_256f
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaShake_256f() {
+ super(SlhDsa.SLH_DSA_SHAKE_256F);
+ }
+ }
+
+ /** SLH-DSA-SHA2-128s only. */
+ public static final class wcSlhDsaSha2_128s
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaSha2_128s() {
+ super(SlhDsa.SLH_DSA_SHA2_128S);
+ }
+ }
+
+ /** SLH-DSA-SHA2-128f only. */
+ public static final class wcSlhDsaSha2_128f
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaSha2_128f() {
+ super(SlhDsa.SLH_DSA_SHA2_128F);
+ }
+ }
+
+ /** SLH-DSA-SHA2-192s only. */
+ public static final class wcSlhDsaSha2_192s
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaSha2_192s() {
+ super(SlhDsa.SLH_DSA_SHA2_192S);
+ }
+ }
+
+ /** SLH-DSA-SHA2-192f only. */
+ public static final class wcSlhDsaSha2_192f
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaSha2_192f() {
+ super(SlhDsa.SLH_DSA_SHA2_192F);
+ }
+ }
+
+ /** SLH-DSA-SHA2-256s only. */
+ public static final class wcSlhDsaSha2_256s
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaSha2_256s() {
+ super(SlhDsa.SLH_DSA_SHA2_256S);
+ }
+ }
+
+ /** SLH-DSA-SHA2-256f only. */
+ public static final class wcSlhDsaSha2_256f
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcSlhDsaSha2_256f() {
+ super(SlhDsa.SLH_DSA_SHA2_256F);
+ }
+ }
+
+ /** Generic HashSLH-DSA (pre-hash) Signature, accepts any parameter set. */
+ public static final class wcHashSlhDsa extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsa() {
+ super(PARAM_ANY, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHA2-128s-WITH-SHA256 only. */
+ public static final class wcHashSlhDsaSha2_128sWithSha256
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaSha2_128sWithSha256() {
+ super(SlhDsa.SLH_DSA_SHA2_128S, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHA2-128f-WITH-SHA256 only. */
+ public static final class wcHashSlhDsaSha2_128fWithSha256
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaSha2_128fWithSha256() {
+ super(SlhDsa.SLH_DSA_SHA2_128F, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHA2-192s-WITH-SHA512 only. */
+ public static final class wcHashSlhDsaSha2_192sWithSha512
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaSha2_192sWithSha512() {
+ super(SlhDsa.SLH_DSA_SHA2_192S, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHA2-192f-WITH-SHA512 only. */
+ public static final class wcHashSlhDsaSha2_192fWithSha512
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaSha2_192fWithSha512() {
+ super(SlhDsa.SLH_DSA_SHA2_192F, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHA2-256s-WITH-SHA512 only. */
+ public static final class wcHashSlhDsaSha2_256sWithSha512
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaSha2_256sWithSha512() {
+ super(SlhDsa.SLH_DSA_SHA2_256S, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHA2-256f-WITH-SHA512 only. */
+ public static final class wcHashSlhDsaSha2_256fWithSha512
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaSha2_256fWithSha512() {
+ super(SlhDsa.SLH_DSA_SHA2_256F, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHAKE-128s-WITH-SHAKE128 only. */
+ public static final class wcHashSlhDsaShake_128sWithShake128
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaShake_128sWithShake128() {
+ super(SlhDsa.SLH_DSA_SHAKE_128S, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHAKE-128f-WITH-SHAKE128 only. */
+ public static final class wcHashSlhDsaShake_128fWithShake128
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaShake_128fWithShake128() {
+ super(SlhDsa.SLH_DSA_SHAKE_128F, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHAKE-192s-WITH-SHAKE256 only. */
+ public static final class wcHashSlhDsaShake_192sWithShake256
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaShake_192sWithShake256() {
+ super(SlhDsa.SLH_DSA_SHAKE_192S, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHAKE-192f-WITH-SHAKE256 only. */
+ public static final class wcHashSlhDsaShake_192fWithShake256
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaShake_192fWithShake256() {
+ super(SlhDsa.SLH_DSA_SHAKE_192F, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHAKE-256s-WITH-SHAKE256 only. */
+ public static final class wcHashSlhDsaShake_256sWithShake256
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaShake_256sWithShake256() {
+ super(SlhDsa.SLH_DSA_SHAKE_256S, true);
+ }
+ }
+
+ /** HashSLH-DSA SLH-DSA-SHAKE-256f-WITH-SHAKE256 only. */
+ public static final class wcHashSlhDsaShake_256fWithShake256
+ extends WolfCryptSlhDsaSignature {
+ /** Default constructor. */
+ public wcHashSlhDsaShake_256fWithShake256() {
+ super(SlhDsa.SLH_DSA_SHAKE_256F, true);
+ }
+ }
+
+ /**
+ * Release any previously-loaded native key (subsequent init resets state).
+ */
+ private void releaseKey() {
+ if (this.key != null) {
+ this.key.releaseNativeStruct();
+ this.key = null;
+ }
+ }
+
+ private void resetForInit() {
+ releaseKey();
+ resetBuffer();
+ }
+
+ /**
+ * Clear the message buffer, reallocating when the prior message was large
+ * so the high-water-mark backing array is not retained for the life of
+ * this Signature object.
+ */
+ private void resetBuffer() {
+ if (this.buffer.size() > BUFFER_RETAIN_MAX) {
+ this.buffer = new ByteArrayOutputStream();
+ }
+ else {
+ this.buffer.reset();
+ }
+ }
+
+ private SlhDsa importPublicKeyForVerify(PublicKey pub)
+ throws InvalidKeyException {
+
+ /* Native wolfJCE public key, parameter set already known. */
+ if (pub instanceof WolfCryptSlhDsaPublicKey) {
+ WolfCryptSlhDsaPublicKey wp = (WolfCryptSlhDsaPublicKey) pub;
+ int param = wp.getParam();
+
+ checkParamMatchesRequired(param);
+
+ return importKeyDer(wp.getEncoded(), param, true,
+ "Failed to import SLH-DSA public key");
+ }
+
+ /* Foreign key, must be X.509 SPKI form. */
+ if (!"X.509".equalsIgnoreCase(pub.getFormat())) {
+ throw new InvalidKeyException(
+ "Unsupported PublicKey format for SLH-DSA: " +
+ pub.getFormat());
+ }
+
+ byte[] der = pub.getEncoded();
+ if (der == null || der.length == 0) {
+ throw new InvalidKeyException(
+ "Cannot extract X.509 SPKI from PublicKey");
+ }
+
+ /* Remove NULL AlgorithmIdentifier (JDK re-encoding) if present */
+ der = WolfCryptSpkiUtil.stripNullAlgIdParams(der);
+
+ return importKeyDer(der, PARAM_ANY, true,
+ "Not a recognized SLH-DSA X.509 SPKI key");
+ }
+
+ private SlhDsa importPrivateKeyForSign(PrivateKey priv)
+ throws InvalidKeyException {
+
+ /* Native wolfJCE private key, parameter set already known. */
+ if (priv instanceof WolfCryptSlhDsaPrivateKey) {
+ WolfCryptSlhDsaPrivateKey wp = (WolfCryptSlhDsaPrivateKey) priv;
+ int param = wp.getParam();
+
+ checkParamMatchesRequired(param);
+
+ return importKeyDer(wp.getEncoded(), param, false,
+ "Failed to import SLH-DSA private key");
+ }
+
+ if (!"PKCS#8".equalsIgnoreCase(priv.getFormat())) {
+ throw new InvalidKeyException(
+ "Unsupported PrivateKey format for SLH-DSA: " +
+ priv.getFormat());
+ }
+
+ byte[] der = priv.getEncoded();
+ if (der == null || der.length == 0) {
+ throw new InvalidKeyException(
+ "Cannot extract PKCS#8 from PrivateKey");
+ }
+
+ return importKeyDer(der, PARAM_ANY, false,
+ "Not a recognized SLH-DSA PKCS#8 key");
+ }
+
+ /**
+ * Import key DER into a new native SlhDsa object, releasing the native
+ * struct before throwing on any failure.
+ *
+ * When param is PARAM_ANY the parameter set is detected by wolfSSL
+ * from DER AlgorithmIdentifier OID and checked against requiredParam
+ * after import.
+ *
+ * @param der key DER, X.509 SPKI if isPublic, otherwise PKCS#8
+ * @param param known parameter set, or {@link #PARAM_ANY} to detect
+ * @param isPublic true for public key import, false for private
+ * @param errMsg InvalidKeyException message on import failure
+ *
+ * @return new SlhDsa object holding the imported key, caller owns the
+ * native struct
+ *
+ * @throws InvalidKeyException if the DER cannot be imported or the
+ * parameter set does not match a parameter-set specific Signature
+ */
+ private SlhDsa importKeyDer(byte[] der, int param, boolean isPublic,
+ String errMsg) throws InvalidKeyException {
+
+ SlhDsa k = null;
+
+ try {
+ if (param != PARAM_ANY) {
+ k = new SlhDsa(param);
+ importDer(k, der, isPublic);
+ }
+ else {
+ /* Parameter set detected by wolfSSL from DER AlgoID OID */
+ k = new SlhDsa();
+ importDer(k, der, isPublic);
+ checkParamMatchesRequired(k.getParam());
+ }
+
+ return k;
+ }
+ catch (WolfCryptException | InvalidKeyException e) {
+ if (k != null) {
+ k.releaseNativeStruct();
+ }
+ if (e instanceof InvalidKeyException) {
+ throw (InvalidKeyException)e;
+ }
+ throw new InvalidKeyException(errMsg, e);
+ }
+ }
+
+ /* Run the native public or private DER import on key object */
+ private static void importDer(SlhDsa k, byte[] der, boolean isPublic) {
+
+ if (isPublic) {
+ k.importPublicKeyDer(der);
+ }
+ else {
+ k.importPrivateKeyDer(der);
+ }
+ }
+
+ private void checkParamMatchesRequired(int keyParam)
+ throws InvalidKeyException {
+
+ if (this.requiredParam == PARAM_ANY) {
+ return; /* generic wcSlhDsa: any parameter set OK */
+ }
+
+ if (keyParam != this.requiredParam) {
+ throw new InvalidKeyException(
+ "Key parameter set does not match Signature: expected " +
+ WolfPQCJdkCompat.slhDsaParamToName(this.requiredParam) +
+ ", got " + WolfPQCJdkCompat.slhDsaParamToName(keyParam));
+ }
+ }
+
+ private Rng getOrInitRng() {
+
+ if (this.rng == null) {
+ this.rng = new Rng();
+ this.rng.init();
+ }
+
+ return this.rng;
+ }
+
+ @Override
+ protected void engineInitVerify(PublicKey publicKey)
+ throws InvalidKeyException {
+
+ if (publicKey == null) {
+ throw new InvalidKeyException("PublicKey is null");
+ }
+
+ resetForInit();
+ this.key = importPublicKeyForVerify(publicKey);
+ this.signing = false;
+ }
+
+ @Override
+ protected void engineInitSign(PrivateKey privateKey)
+ throws InvalidKeyException {
+
+ if (privateKey == null) {
+ throw new InvalidKeyException("PrivateKey is null");
+ }
+
+ resetForInit();
+ this.key = importPrivateKeyForSign(privateKey);
+ this.signing = true;
+ }
+
+ @Override
+ protected void engineUpdate(byte b) throws SignatureException {
+
+ if (this.key == null) {
+ throw new SignatureException("Signature not initialized");
+ }
+
+ this.buffer.write(b);
+ }
+
+ @Override
+ protected void engineUpdate(byte[] b, int off, int len)
+ throws SignatureException {
+
+ if (this.key == null) {
+ throw new SignatureException("Signature not initialized");
+ }
+
+ /* Use subtraction form so a large off/len pair cannot overflow
+ * int and slip past the bounds check */
+ if (b == null || off < 0 || len < 0 || len > b.length - off) {
+ throw new SignatureException("Invalid update arguments");
+ }
+
+ this.buffer.write(b, off, len);
+ }
+
+ @Override
+ protected byte[] engineSign() throws SignatureException {
+
+ if (this.key == null || !this.signing) {
+ throw new SignatureException(
+ "Signature not initialized for signing");
+ }
+
+ try {
+ byte[] msg = this.buffer.toByteArray();
+
+ if (this.preHash) {
+ return this.key.signPreHash(msg, this.context,
+ getOrInitRng());
+ }
+
+ return this.key.sign(msg, this.context, getOrInitRng());
+ }
+ catch (WolfCryptException e) {
+ throw new SignatureException("SLH-DSA sign failed", e);
+ }
+ finally {
+ resetBuffer();
+ }
+ }
+
+ @Override
+ protected boolean engineVerify(byte[] sigBytes)
+ throws SignatureException {
+
+ if (this.key == null || this.signing) {
+ throw new SignatureException(
+ "Signature not initialized for verification");
+ }
+
+ if (sigBytes == null) {
+ throw new SignatureException("Signature bytes are null");
+ }
+
+ try {
+ byte[] msg = this.buffer.toByteArray();
+
+ if (this.preHash) {
+ return this.key.verifyPreHash(sigBytes, msg, this.context);
+ }
+
+ return this.key.verify(sigBytes, msg, this.context);
+ }
+ catch (WolfCryptException e) {
+ throw new SignatureException("SLH-DSA verify failed", e);
+ }
+ finally {
+ resetBuffer();
+ }
+ }
+
+ /**
+ * @deprecated unsupported. Use
+ * {@code engineSetParameter(AlgorithmParameterSpec)} with a
+ * {@link WolfCryptContextParameterSpec} to set a context.
+ */
+ @Override
+ @Deprecated
+ protected void engineSetParameter(String param, Object value)
+ throws InvalidParameterException {
+
+ throw new InvalidParameterException(
+ "Use setParameter(AlgorithmParameterSpec) with a " +
+ "WolfCryptContextParameterSpec to set an SLH-DSA context");
+ }
+
+ @Override
+ protected void engineSetParameter(AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+
+ if (params == null) {
+ throw new InvalidAlgorithmParameterException(
+ "AlgorithmParameterSpec cannot be null, use a " +
+ "WolfCryptContextParameterSpec to set or reset the " +
+ "SLH-DSA context");
+ }
+
+ if (params instanceof WolfCryptContextParameterSpec) {
+ this.context = ((WolfCryptContextParameterSpec) params)
+ .getContext();
+ return;
+ }
+
+ throw new InvalidAlgorithmParameterException(
+ "SLH-DSA only accepts WolfCryptContextParameterSpec, got: " +
+ params.getClass().getName());
+ }
+
+ /**
+ * @deprecated unsupported.
+ */
+ @Override
+ @Deprecated
+ protected Object engineGetParameter(String param)
+ throws InvalidParameterException {
+
+ throw new InvalidParameterException(
+ "SLH-DSA does not support getParameter(String)");
+ }
+}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSpkiUtil.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSpkiUtil.java
new file mode 100644
index 00000000..f17b96ac
--- /dev/null
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSpkiUtil.java
@@ -0,0 +1,469 @@
+/* WolfCryptSpkiUtil.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+
+/**
+ * ASN.1/DER X.509 SubjectPublicKeyInfo helpers for the hash-based signature
+ * public keys wolfJCE wraps in Java: LMS/HSS (RFC 8554) and XMSS/XMSS^MT
+ * (RFC 8391). Native wolfCrypt only provides raw public-key import/export
+ * for these algorithms (wc_LmsKey/wc_XmssKey Export/ImportPubRaw()) and no
+ * SPKI decode, so the SPKI wrapping is done here until native wolfSSL gets
+ * that functionality.
+ *
+ * DER encoding reuses the shared {@link WolfCryptASN1Util}
+ * helpers. DER decoding uses the strict reader here: it checks every
+ * tag and rejects indefinite and non-minimal lengths, because it parses
+ * untrusted X.509 SubjectPublicKeyInfo input. This reader is intentionally
+ * separate from and stricter than the general-purpose
+ * {@link WolfCryptASN1Util}.
+ *
+ * SubjectPublicKeyInfo layout produced and parsed here:
+ *
+ * SEQUENCE {
+ * algorithm SEQUENCE { OBJECT IDENTIFIER } -- params absent or NULL
+ * subjectPublicKey BIT STRING -- raw public key
+ * }
+ *
+ *
+ * Encoding places the raw key directly in the BIT STRING: the RFC 9708
+ * form for LMS ({@code id-alg-hss-lms-hashsig = 1.2.840.113549.1.9.16.3.17},
+ * assigned in RFC 8708 and retained in RFC 9708) and the RFC 9802 form for
+ * XMSS/XMSS^MT ({@code id-alg-xmss-hashsig = 1.3.6.1.5.5.7.6.34},
+ * {@code id-alg-xmssmt-hashsig = 1.3.6.1.5.5.7.6.35}). Parsing also accepts
+ * the raw key wrapped in an inner OCTET STRING (the RFC 8708 / JDK <= 23
+ * form): the raw LMS and XMSS public keys always begin with a 4-byte
+ * big-endian word whose first byte is 0x00, so a leading 0x04 unambiguously
+ * means the wrapped form. Parsing also tolerates an explicit ASN.1 NULL in
+ * the AlgorithmIdentifier parameters, which the JDK X.509 stack re-encodes
+ * even where the RFCs say absent.
+ *
+ * wolfJCE provides verify-only LMS and XMSS support, so there are no
+ * private-key encodings here (matching the JDK SUN provider posture).
+ */
+final class WolfCryptSpkiUtil {
+
+ /* DER tags used here. */
+ private static final int TAG_BIT_STRING = 0x03;
+ private static final int TAG_OCTET_STRING = 0x04;
+ private static final int TAG_NULL = 0x05;
+ private static final int TAG_OID = 0x06;
+ private static final int TAG_SEQUENCE = 0x30;
+
+ /* OID content bytes (no tag/length) for
+ * 1.2.840.113549.1.9.16.3.17 (id-alg-hss-lms-hashsig). */
+ private static final byte[] OID_HSS_LMS = {
+ (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xF7,
+ (byte)0x0D, (byte)0x01, (byte)0x09, (byte)0x10, (byte)0x03,
+ (byte)0x11
+ };
+
+ /* OID content bytes (no tag/length) for
+ * 1.3.6.1.5.5.7.6.34 (id-alg-xmss-hashsig). */
+ private static final byte[] OID_XMSS = {
+ (byte)0x2B, (byte)0x06, (byte)0x01, (byte)0x05, (byte)0x05,
+ (byte)0x07, (byte)0x06, (byte)0x22
+ };
+
+ /* OID content bytes (no tag/length) for
+ * 1.3.6.1.5.5.7.6.35 (id-alg-xmssmt-hashsig). */
+ private static final byte[] OID_XMSSMT = {
+ (byte)0x2B, (byte)0x06, (byte)0x01, (byte)0x05, (byte)0x05,
+ (byte)0x07, (byte)0x06, (byte)0x23
+ };
+
+ /* Index of OID_XMSSMT in the allowed-OID array used by
+ * parseXmssPublicKeyDer(). */
+ private static final int OID_INDEX_XMSSMT = 1;
+
+ /** Private constructor, all methods are static. */
+ private WolfCryptSpkiUtil() {
+ }
+
+ /**
+ * If the SubjectPublicKeyInfo AlgorithmIdentifier carries an explicit
+ * NULL parameters field, return a copy re-encoded with the parameters
+ * absent, otherwise return the input unchanged.
+ *
+ * JDK X.509 stack can re-encode an absent AlgorithmIdentifier param
+ * field as an explicit ASN.1 NULL when routing a certificate public key
+ * to a KeyFactory by OID. Native wolfSSL SLH-DSA SPKI decode requires
+ * the parameters to be absent (RFC 9909), so callers normalize with this
+ * before handing DER to native.
+ *
+ * @param spki X.509 SubjectPublicKeyInfo DER
+ *
+ * @return spki with an explicit NULL AlgorithmIdentifier parameters
+ * field removed, or the input reference unchanged
+ */
+ static byte[] stripNullAlgIdParams(byte[] spki) {
+
+ int[] outer, algId, oid, nullParam;
+ byte[] oidTlv;
+ byte[] restTlv;
+ byte[] newAlgId;
+
+ if (spki == null || spki.length == 0) {
+ return spki;
+ }
+
+ try {
+ /* outer SEQUENCE, AlgorithmIdentifier SEQUENCE { OID ... } */
+ outer = readTLV(spki, 0, spki.length, TAG_SEQUENCE, "SPKI");
+ algId = readTLV(spki, outer[0], outer[1], TAG_SEQUENCE, "SPKI");
+ oid = readTLV(spki, algId[0], algId[1], TAG_OID, "SPKI");
+
+ if (oid[1] == algId[1]) {
+ /* parameters already absent */
+ return spki;
+ }
+
+ /* Only rewrite the single explicit NULL (05 00) form. */
+ nullParam = readTLV(spki, oid[1], algId[1], TAG_NULL, "SPKI");
+ if (nullParam[0] != nullParam[1] || nullParam[1] != algId[1]) {
+ return spki;
+ }
+ }
+ catch (IllegalArgumentException e) {
+ /* not the expected shape, let the native decoder report it */
+ return spki;
+ }
+
+ /* Rebuild: SEQUENCE { SEQUENCE { OID }, }. The
+ * OID TLV starts at the AlgorithmIdentifier content start, the
+ * rest (subjectPublicKey BIT STRING) follows the old algId TLV. */
+ oidTlv = Arrays.copyOfRange(spki, algId[0], oid[1]);
+ restTlv = Arrays.copyOfRange(spki, algId[1], outer[1]);
+ newAlgId = WolfCryptASN1Util.encodeDERSequence(oidTlv);
+
+ return WolfCryptASN1Util.encodeDERSequence(
+ concat(newAlgId, restTlv));
+ }
+
+ /**
+ * Result of parsing an XMSS SubjectPublicKeyInfo: the raw RFC 8391
+ * public key bytes and the family (single-tree XMSS vs multi-tree
+ * XMSS^MT), taken from the AlgorithmIdentifier OID. The family is needed
+ * because a raw XMSS public key's 4-byte OID prefix is ambiguous between
+ * the two registries.
+ */
+ static final class ParsedXmssPub {
+ /** Raw RFC 8391 public key ({@code OID || root || SEED}). */
+ final byte[] raw;
+ /** True if the AlgorithmIdentifier was id-alg-xmssmt-hashsig. */
+ final boolean isXmssMt;
+
+ ParsedXmssPub(byte[] raw, boolean isXmssMt) {
+ this.raw = raw;
+ this.isXmssMt = isXmssMt;
+ }
+ }
+
+ /**
+ * Encode a raw HSS/LMS public key as an X.509 SubjectPublicKeyInfo DER,
+ * RFC 9708 unwrapped form (raw key directly in the BIT STRING).
+ *
+ * @param rawPub raw HSS/LMS public key bytes
+ *
+ * @return X.509 SubjectPublicKeyInfo DER
+ */
+ static byte[] encodeLmsPublicKeyDer(byte[] rawPub) {
+
+ return encodeSpki(rawPub, OID_HSS_LMS);
+ }
+
+ /**
+ * Parse an X.509 SubjectPublicKeyInfo DER carrying an LMS/HSS public
+ * key and return the raw HSS/LMS public key bytes. Accepts both the
+ * RFC 9708 unwrapped form and the RFC 8708 form (raw key wrapped in an
+ * inner OCTET STRING).
+ *
+ * @param x509 X.509 SubjectPublicKeyInfo DER
+ *
+ * @return raw HSS/LMS public key bytes
+ *
+ * @throws IllegalArgumentException if the DER is malformed or not an
+ * LMS SubjectPublicKeyInfo
+ */
+ static byte[] parseLmsPublicKeyDer(byte[] x509) {
+
+ return parseSpki(x509, new byte[][] { OID_HSS_LMS }, "LMS").raw;
+ }
+
+ /**
+ * Encode a raw XMSS/XMSS^MT public key as an X.509 SubjectPublicKeyInfo
+ * DER, RFC 9802 form (raw key directly in the BIT STRING).
+ *
+ * @param rawPub raw XMSS public key bytes
+ * @param isXmssMt true to use the XMSS^MT OID, false for single-tree
+ * XMSS
+ *
+ * @return X.509 SubjectPublicKeyInfo DER
+ */
+ static byte[] encodeXmssPublicKeyDer(byte[] rawPub, boolean isXmssMt) {
+
+ return encodeSpki(rawPub, isXmssMt ? OID_XMSSMT : OID_XMSS);
+ }
+
+ /**
+ * Parse an X.509 SubjectPublicKeyInfo DER carrying an XMSS or XMSS^MT
+ * public key and return the raw RFC 8391 public key bytes and family.
+ * Accepts either the {@code id-alg-xmss-hashsig} or
+ * {@code id-alg-xmssmt-hashsig} OID, and both the RFC 9802 unwrapped
+ * form and an inner OCTET STRING wrapped form.
+ *
+ * @param x509 X.509 SubjectPublicKeyInfo DER
+ *
+ * @return parsed raw XMSS public key bytes and family
+ *
+ * @throws IllegalArgumentException if the DER is malformed or not an
+ * XMSS SubjectPublicKeyInfo
+ */
+ static ParsedXmssPub parseXmssPublicKeyDer(byte[] x509) {
+
+ ParsedSpki parsed = parseSpki(x509,
+ new byte[][] { OID_XMSS, OID_XMSSMT }, "XMSS");
+
+ return new ParsedXmssPub(parsed.raw,
+ parsed.oidIndex == OID_INDEX_XMSSMT);
+ }
+
+ /* Raw public key bytes plus the index (into the allowed-OID array) of
+ * the AlgorithmIdentifier OID that matched. */
+ private static final class ParsedSpki {
+
+ final byte[] raw;
+ final int oidIndex;
+
+ ParsedSpki(byte[] raw, int oidIndex) {
+ this.raw = raw;
+ this.oidIndex = oidIndex;
+ }
+ }
+
+ /*
+ * Encode a raw public key as SubjectPublicKeyInfo DER with the raw key
+ * directly in the BIT STRING and no AlgorithmIdentifier parameters.
+ */
+ private static byte[] encodeSpki(byte[] rawPub, byte[] oidContent) {
+
+ byte[] algId;
+ byte[] bitString;
+
+ if (rawPub == null || rawPub.length == 0) {
+ throw new IllegalArgumentException(
+ "raw public key cannot be null or empty");
+ }
+
+ algId = WolfCryptASN1Util.encodeDERSequence(
+ WolfCryptASN1Util.encodeDERObjectIdentifier(oidContent));
+
+ /* The raw key sits directly in the BIT STRING (encodeDERBitString
+ * prepends the 0x00 unused-bits octet). */
+ bitString = WolfCryptASN1Util.encodeDERBitString(rawPub);
+
+ return WolfCryptASN1Util.encodeDERSequence(concat(algId, bitString));
+ }
+
+ /*
+ * Strict SubjectPublicKeyInfo parse. Returns the raw public key bytes
+ * from the BIT STRING (unwrapping one inner OCTET STRING if present)
+ * and which of the allowed AlgorithmIdentifier OIDs matched. algLabel
+ * is used to prefix parse error messages.
+ */
+ private static ParsedSpki parseSpki(byte[] x509, byte[][] allowedOids,
+ String algLabel) {
+
+ int end;
+ int bsStart;
+ int bsEnd;
+ int contentStart;
+ int oidIndex = -1;
+ int[] spki, algId, oid, bitStr, oct, nullParam;
+
+ if (x509 == null || x509.length == 0) {
+ throw new IllegalArgumentException(
+ "encoded key cannot be null or empty");
+ }
+
+ /* outer SubjectPublicKeyInfo SEQUENCE, no trailing data */
+ spki = readTLV(x509, 0, x509.length, TAG_SEQUENCE, algLabel);
+ if (spki[1] != x509.length) {
+ throw bad(algLabel, "trailing data after SubjectPublicKeyInfo");
+ }
+ end = spki[1];
+
+ /* AlgorithmIdentifier SEQUENCE { OID [, NULL] }. Parameters should
+ * be absent, but the JDK X.509 stack re-encodes them as an explicit
+ * NULL, so a single trailing NULL is also tolerated. */
+ algId = readTLV(x509, spki[0], end, TAG_SEQUENCE, algLabel);
+ oid = readTLV(x509, algId[0], algId[1], TAG_OID, algLabel);
+ for (int i = 0; i < allowedOids.length; i++) {
+ if (regionEquals(x509, oid[0], oid[1], allowedOids[i])) {
+ oidIndex = i;
+ break;
+ }
+ }
+ if (oidIndex < 0) {
+ throw bad(algLabel, "unexpected AlgorithmIdentifier OID");
+ }
+ if (oid[1] != algId[1]) {
+ /* The parameters must be a single NULL (05 00) */
+ nullParam = readTLV(x509, oid[1], algId[1], TAG_NULL, algLabel);
+ if (nullParam[0] != nullParam[1] || nullParam[1] != algId[1]) {
+ throw bad(algLabel,
+ "unexpected AlgorithmIdentifier parameters");
+ }
+ }
+
+ /* subjectPublicKey BIT STRING, no trailing data */
+ bitStr = readTLV(x509, algId[1], end, TAG_BIT_STRING, algLabel);
+ if (bitStr[1] != end) {
+ throw bad(algLabel, "trailing data after subjectPublicKey");
+ }
+ bsStart = bitStr[0];
+ bsEnd = bitStr[1];
+ if (bsStart >= bsEnd) {
+ throw bad(algLabel, "empty subjectPublicKey BIT STRING");
+ }
+ if ((x509[bsStart] & 0xFF) != 0x00) {
+ throw bad(algLabel, "unexpected unused bits in subjectPublicKey");
+ }
+ contentStart = bsStart + 1;
+ if (contentStart >= bsEnd) {
+ throw bad(algLabel, "missing public key in subjectPublicKey");
+ }
+
+ /* An inner OCTET STRING wrapper means the RFC 8708 / older-JDK
+ * form. The raw LMS and XMSS keys start with a 0x00 byte, so a
+ * leading 0x04 unambiguously means the wrapped form. */
+ if ((x509[contentStart] & 0xFF) == TAG_OCTET_STRING) {
+ oct = readTLV(x509, contentStart, bsEnd, TAG_OCTET_STRING,
+ algLabel);
+ if (oct[1] != bsEnd) {
+ throw bad(algLabel, "trailing data in wrapped public key");
+ }
+ return new ParsedSpki(
+ Arrays.copyOfRange(x509, oct[0], oct[1]), oidIndex);
+ }
+
+ return new ParsedSpki(
+ Arrays.copyOfRange(x509, contentStart, bsEnd), oidIndex);
+ }
+
+ /*
+ * Parse a definite-form DER TLV at offset 'off' within [off, limit),
+ * requiring the given tag. Returns {contentStart, contentEnd}. Rejects
+ * indefinite/non-minimal lengths and overruns.
+ */
+ private static int[] readTLV(byte[] in, int off, int limit, int tag,
+ String algLabel) {
+
+ int lenByte, contentStart, length, numLenBytes;
+
+ if (off + 2 > limit) {
+ throw bad(algLabel, "truncated TLV header");
+ }
+
+ if ((in[off] & 0xFF) != tag) {
+ throw bad(algLabel, "unexpected tag 0x" +
+ Integer.toHexString(in[off] & 0xFF) + ", expected 0x" +
+ Integer.toHexString(tag));
+ }
+
+ lenByte = in[off + 1] & 0xFF;
+
+ if (lenByte < 0x80) {
+ length = lenByte;
+ contentStart = off + 2;
+ }
+ else {
+ numLenBytes = lenByte & 0x7F;
+
+ if (numLenBytes == 0 || numLenBytes > 4) {
+ throw bad(algLabel, "unsupported DER length form");
+ }
+
+ if (off + 2 + numLenBytes > limit) {
+ throw bad(algLabel, "truncated DER length");
+ }
+ length = 0;
+
+ for (int i = 0; i < numLenBytes; i++) {
+ length = (length << 8) | (in[off + 2 + i] & 0xFF);
+ }
+
+ /* Values < 0x80 must use the short form, and an n-octet length
+ * must actually need n octets (no leading zero octets). */
+ if (length < 0x80 ||
+ length < (1 << ((numLenBytes - 1) * 8))) {
+ throw bad(algLabel, "non-minimal DER length");
+ }
+
+ contentStart = off + 2 + numLenBytes;
+ }
+
+ /* contentStart <= limit was checked above, so limit - contentStart
+ * is non-negative and a crafted ~2GB long-form length cannot
+ * overflow the comparison. */
+ if ((length < 0) || (length > limit - contentStart)) {
+ throw bad(algLabel, "DER length exceeds buffer");
+ }
+
+ return new int[] { contentStart, contentStart + length };
+ }
+
+ /* Compare a region of 'in' against 'expected'. */
+ private static boolean regionEquals(byte[] in, int start, int end,
+ byte[] expected) {
+
+ if ((end - start) != expected.length) {
+ return false;
+ }
+
+ for (int i = 0; i < expected.length; i++) {
+ if (in[start + i] != expected[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /* Concatenate byte arrays. */
+ private static byte[] concat(byte[]... parts) {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (byte[] p : parts) {
+ out.write(p, 0, p.length);
+ }
+
+ return out.toByteArray();
+ }
+
+ private static IllegalArgumentException bad(String algLabel, String msg) {
+ return new IllegalArgumentException(
+ algLabel + " DER parse error: " + msg);
+ }
+}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java
index f73cdb0e..a55adba9 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java
@@ -43,6 +43,7 @@
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
+import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
@@ -821,5 +822,30 @@ public static KeyFactory getKeyFactoryPreferWolfJCE(String algorithm)
return KeyFactory.getInstance(algorithm);
}
}
+
+ /**
+ * Return key.getEncoded(), throwing InvalidKeySpecException if the key
+ * has no encoded form.
+ *
+ * @param key Key to get the encoding from
+ * @param expectedFormat encoding format name used in the exception
+ * message (ex: "PKCS#8" or "X.509")
+ *
+ * @return encoded key bytes
+ *
+ * @throws InvalidKeySpecException if key.getEncoded() is null or empty
+ */
+ static byte[] requireEncoded(Key key, String expectedFormat)
+ throws InvalidKeySpecException {
+
+ byte[] encoded = key.getEncoded();
+
+ if (encoded == null || encoded.length == 0) {
+ throw new InvalidKeySpecException(
+ "Key has no encoded form (expected " + expectedFormat + ")");
+ }
+
+ return encoded;
+ }
}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssKeyFactory.java
index 139ff33a..3f6a542e 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssKeyFactory.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssKeyFactory.java
@@ -101,6 +101,7 @@ protected T engineGetKeySpec(Key key, Class keySpec)
throws InvalidKeySpecException {
byte[] encoded;
+ Key wolfKey;
if (key == null) {
throw new InvalidKeySpecException("Key cannot be null");
@@ -111,14 +112,21 @@ protected T engineGetKeySpec(Key key, Class keySpec)
"Requested KeySpec class cannot be null");
}
- if (key instanceof PublicKey) {
+ /* Normalize key, validates foreign keys (encoding, type, DER) */
+ try {
+ wolfKey = engineTranslateKey(key);
+ } catch (InvalidKeyException e) {
+ throw new InvalidKeySpecException(e.getMessage(), e);
+ }
+
+ if (wolfKey instanceof PublicKey) {
if (!keySpec.isAssignableFrom(X509EncodedKeySpec.class)) {
throw new InvalidKeySpecException(
"XMSS public keys can only be expressed as " +
"X509EncodedKeySpec, got request for: " +
keySpec.getName());
}
- encoded = requireEncoded(key, "X.509");
+ encoded = WolfCryptUtil.requireEncoded(wolfKey, "X.509");
return keySpec.cast(new X509EncodedKeySpec(encoded));
}
@@ -168,17 +176,4 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException {
"Unsupported Key type: " + key.getClass().getName());
}
- /* Return key.getEncoded(), throws if null/empty. */
- private static byte[] requireEncoded(Key key, String expectedFormat)
- throws InvalidKeySpecException {
-
- byte[] encoded = key.getEncoded();
-
- if (encoded == null || encoded.length == 0) {
- throw new InvalidKeySpecException(
- "Key has no encoded form (expected " + expectedFormat + ")");
- }
-
- return encoded;
- }
}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssPublicKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssPublicKey.java
index 51a1bf50..04cd850d 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssPublicKey.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssPublicKey.java
@@ -76,7 +76,7 @@ public WolfCryptXmssPublicKey(byte[] x509Der)
throws IllegalArgumentException {
byte[] rawPub;
- WolfCryptXmssUtil.ParsedPub parsed;
+ WolfCryptSpkiUtil.ParsedXmssPub parsed;
Xmss key = null;
if (x509Der == null || x509Der.length == 0) {
@@ -87,7 +87,7 @@ public WolfCryptXmssPublicKey(byte[] x509Der)
/* Extract the raw XMSS public key and the family (XMSS vs XMSS^MT,
* from the SPKI OID), then validate it by importing through wolfCrypt,
* which derives the specific parameter set. */
- parsed = WolfCryptXmssUtil.parsePublicKeyDer(x509Der);
+ parsed = WolfCryptSpkiUtil.parseXmssPublicKeyDer(x509Der);
rawPub = parsed.raw;
try {
@@ -108,7 +108,7 @@ public WolfCryptXmssPublicKey(byte[] x509Der)
/* Normalize to the canonical RFC 9802 SubjectPublicKeyInfo, so
* getEncoded() and equals() are stable across input encodings. */
- this.encoded = WolfCryptXmssUtil.encodePublicKeyDer(rawPub,
+ this.encoded = WolfCryptSpkiUtil.encodeXmssPublicKeyDer(rawPub,
this.isXmssMt);
this.rawPublicKey = rawPub.clone();
}
@@ -288,9 +288,9 @@ private void readObject(ObjectInputStream in)
in.defaultReadObject();
stateLock = new Object();
if (!destroyed && encoded != null) {
- WolfCryptXmssUtil.ParsedPub parsed;
+ WolfCryptSpkiUtil.ParsedXmssPub parsed;
try {
- parsed = WolfCryptXmssUtil.parsePublicKeyDer(encoded);
+ parsed = WolfCryptSpkiUtil.parseXmssPublicKeyDer(encoded);
}
catch (IllegalArgumentException e) {
throw new InvalidObjectException(
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssSignature.java
index 24042fca..190b8f4d 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssSignature.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssSignature.java
@@ -116,8 +116,8 @@ private Xmss importPublicKeyForVerify(PublicKey pub)
}
try {
- WolfCryptXmssUtil.ParsedPub parsed =
- WolfCryptXmssUtil.parsePublicKeyDer(der);
+ WolfCryptSpkiUtil.ParsedXmssPub parsed =
+ WolfCryptSpkiUtil.parseXmssPublicKeyDer(der);
rawPub = parsed.raw;
isXmssMt = parsed.isXmssMt;
}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssUtil.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssUtil.java
deleted file mode 100644
index 0c3f5346..00000000
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptXmssUtil.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/* WolfCryptXmssUtil.java
- *
- * Copyright (C) 2006-2026 wolfSSL Inc.
- *
- * This file is part of wolfSSL.
- *
- * wolfSSL 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 2 of the License, or
- * (at your option) any later version.
- *
- * wolfSSL 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
- */
-
-package com.wolfssl.provider.jce;
-
-import java.io.ByteArrayOutputStream;
-import java.util.Arrays;
-
-/**
- * ASN.1/DER helpers for XMSS/XMSS^MT (RFC 8391) public keys.
- *
- * Native wolfCrypt provides a raw XMSS public-key format
- * (wc_XmssKey_Export/ImportPubRaw()) and no SPKI decode, so the X.509
- * SubjectPublicKeyInfo wrapping is done here until native wolfSSL gets that
- * functionality. DER encoding reuses the shared
- * {@link WolfCryptASN1Util} helpers. DER decoding uses a small
- * self-contained strict reader. The XMSS algorithm OIDs are RFC 9802
- * {@code id-alg-xmss-hashsig = 1.3.6.1.5.5.7.6.34} and
- * {@code id-alg-xmssmt-hashsig = 1.3.6.1.5.5.7.6.35}.
- *
- * The DER reader here is intentionally self-contained and stricter than the
- * general-purpose {@link WolfCryptASN1Util}: it checks every tag and rejects
- * non-minimal long-form lengths, because it parses untrusted X.509
- * SubjectPublicKeyInfo input.
- *
- * Public key (SubjectPublicKeyInfo):
- *
- * SEQUENCE {
- * algorithm SEQUENCE { OBJECT IDENTIFIER } -- parameters absent
- * subjectPublicKey BIT STRING -- raw XMSS public key
- * }
- *
- * RFC 9802 / BouncyCastle place the raw RFC 8391 public key
- * ({@code 4-byte param-set OID || root || SEED}) directly in the BIT STRING.
- * wolfJCE writes that unwrapped form and reads it. Defensively, an inner
- * OCTET STRING wrapper is also accepted on input: the raw XMSS public key
- * always begins with a 4-byte big-endian parameter-set OID whose first byte is
- * {@code 0x00}, so a leading {@code 0x04} unambiguously means the wrapped form.
- *
- * wolfJCE provides verify-only XMSS support, so there is no private-key
- * encoding here.
- */
-final class WolfCryptXmssUtil {
-
- /* DER tags used here. */
- private static final int TAG_BIT_STRING = 0x03;
- private static final int TAG_OCTET_STRING = 0x04;
- private static final int TAG_NULL = 0x05;
- private static final int TAG_OID = 0x06;
- private static final int TAG_SEQUENCE = 0x30;
-
- /* OID content bytes (no tag/length) for
- * 1.3.6.1.5.5.7.6.34 (id-alg-xmss-hashsig). */
- private static final byte[] OID_XMSS = {
- (byte)0x2B, (byte)0x06, (byte)0x01, (byte)0x05, (byte)0x05,
- (byte)0x07, (byte)0x06, (byte)0x22
- };
-
- /* OID content bytes (no tag/length) for
- * 1.3.6.1.5.5.7.6.35 (id-alg-xmssmt-hashsig). */
- private static final byte[] OID_XMSSMT = {
- (byte)0x2B, (byte)0x06, (byte)0x01, (byte)0x05, (byte)0x05,
- (byte)0x07, (byte)0x06, (byte)0x23
- };
-
- /** Private constructor, all methods are static. */
- private WolfCryptXmssUtil() {
- }
-
- /**
- * Result of parsing an XMSS SubjectPublicKeyInfo: the raw RFC 8391 public
- * key bytes and the family (single-tree XMSS vs multi-tree XMSS^MT), taken
- * from the AlgorithmIdentifier OID. The family is needed because a raw
- * XMSS public key's 4-byte OID prefix is ambiguous between the two
- * registries.
- */
- static final class ParsedPub {
- /** Raw RFC 8391 public key ({@code OID || root || SEED}). */
- final byte[] raw;
- /** True if the AlgorithmIdentifier was id-alg-xmssmt-hashsig. */
- final boolean isXmssMt;
-
- ParsedPub(byte[] raw, boolean isXmssMt) {
- this.raw = raw;
- this.isXmssMt = isXmssMt;
- }
- }
-
- /**
- * Encode a raw XMSS/XMSS^MT public key as an X.509 SubjectPublicKeyInfo
- * DER, RFC 9802 form (raw key directly in the BIT STRING).
- *
- * @param rawPub raw XMSS public key bytes
- * @param isXmssMt true to use the XMSS^MT OID, false for single-tree XMSS
- *
- * @return X.509 SubjectPublicKeyInfo DER
- */
- static byte[] encodePublicKeyDer(byte[] rawPub, boolean isXmssMt) {
-
- byte[] algId;
- byte[] bitString;
-
- if (rawPub == null || rawPub.length == 0) {
- throw new IllegalArgumentException(
- "raw public key cannot be null or empty");
- }
-
- algId = WolfCryptASN1Util.encodeDERSequence(
- WolfCryptASN1Util.encodeDERObjectIdentifier(
- isXmssMt ? OID_XMSSMT : OID_XMSS));
-
- /* RFC 9802 form: the raw key sits directly in the BIT STRING
- * (encodeDERBitString prepends the 0x00 unused-bits octet). */
- bitString = WolfCryptASN1Util.encodeDERBitString(rawPub);
-
- return WolfCryptASN1Util.encodeDERSequence(concat(algId, bitString));
- }
-
- /**
- * Parse an X.509 SubjectPublicKeyInfo DER carrying an XMSS or XMSS^MT
- * public key and return the raw RFC 8391 public key bytes. Accepts either
- * the {@code id-alg-xmss-hashsig} or {@code id-alg-xmssmt-hashsig} OID, and
- * both the RFC 9802 unwrapped form and an inner OCTET STRING wrapped form.
- *
- * @param x509 X.509 SubjectPublicKeyInfo DER
- *
- * @return parsed raw XMSS public key bytes and family
- *
- * @throws IllegalArgumentException if the DER is malformed or not an XMSS
- * SubjectPublicKeyInfo
- */
- static ParsedPub parsePublicKeyDer(byte[] x509) {
-
- int end;
- int bsStart;
- int bsEnd;
- int contentStart;
- boolean isXmssMt;
- int[] spki, algId, oid, bitStr, oct;
-
- if (x509 == null || x509.length == 0) {
- throw new IllegalArgumentException(
- "encoded key cannot be null or empty");
- }
-
- /* outer SubjectPublicKeyInfo SEQUENCE, no trailing data */
- spki = readTLV(x509, 0, x509.length, TAG_SEQUENCE);
- if (spki[1] != x509.length) {
- throw new IllegalArgumentException(
- "trailing data after SubjectPublicKeyInfo");
- }
- end = spki[1];
-
- /* AlgorithmIdentifier SEQUENCE { OID [, parameters] }.
- * OID selects the family (XMSS vs XMSS^MT). */
- algId = readTLV(x509, spki[0], end, TAG_SEQUENCE);
- oid = readTLV(x509, algId[0], algId[1], TAG_OID);
- if (regionEquals(x509, oid[0], oid[1], OID_XMSS)) {
- isXmssMt = false;
- }
- else if (regionEquals(x509, oid[0], oid[1], OID_XMSSMT)) {
- isXmssMt = true;
- }
- else {
- throw new IllegalArgumentException(
- "not an XMSS/XMSS^MT AlgorithmIdentifier OID");
- }
- /* RFC 9802 says parameters field is absent for XMSS/XMSS^MT, but
- * also accept an explicit ASN.1 NULL. JDK X.509 re-encodes the
- * AlgorithmIdentifier with a NULL parameters field when it hands a
- * certificate SubjectPublicKeyInfo to this KeyFactory by OID. */
- if (oid[1] != algId[1]) {
- int[] params = readTLV(x509, oid[1], algId[1], TAG_NULL);
- if (params[0] != params[1] || params[1] != algId[1]) {
- throw new IllegalArgumentException(
- "unexpected AlgorithmIdentifier parameters");
- }
- }
-
- /* subjectPublicKey BIT STRING, no trailing data */
- bitStr = readTLV(x509, algId[1], end, TAG_BIT_STRING);
- if (bitStr[1] != end) {
- throw new IllegalArgumentException(
- "trailing data after subjectPublicKey");
- }
- bsStart = bitStr[0];
- bsEnd = bitStr[1];
- if (bsStart >= bsEnd) {
- throw new IllegalArgumentException(
- "empty subjectPublicKey BIT STRING");
- }
- if ((x509[bsStart] & 0xFF) != 0x00) {
- throw new IllegalArgumentException(
- "unexpected unused bits in subjectPublicKey");
- }
- contentStart = bsStart + 1;
- if (contentStart >= bsEnd) {
- throw new IllegalArgumentException(
- "missing public key in subjectPublicKey");
- }
-
- /* The raw XMSS public key always begins with a 4-byte big-endian
- * parameter-set OID whose first byte is 0x00, so a leading 0x04
- * unambiguously means the key was wrapped in an inner OCTET STRING. */
- if ((x509[contentStart] & 0xFF) == TAG_OCTET_STRING) {
- oct = readTLV(x509, contentStart, bsEnd, TAG_OCTET_STRING);
- if (oct[1] != bsEnd) {
- throw new IllegalArgumentException(
- "trailing data in wrapped public key");
- }
- return new ParsedPub(Arrays.copyOfRange(x509, oct[0], oct[1]),
- isXmssMt);
- }
-
- return new ParsedPub(Arrays.copyOfRange(x509, contentStart, bsEnd),
- isXmssMt);
- }
-
- /*
- * Parse a definite-form DER TLV at offset 'off' within [off, limit),
- * requiring the given tag. Returns {contentStart, contentEnd}. Rejects
- * indefinite/non-minimal lengths and overruns.
- */
- private static int[] readTLV(byte[] in, int off, int limit, int tag) {
-
- int lenByte, contentStart, length, numLenBytes;
-
- if (off + 2 > limit) {
- throw new IllegalArgumentException(
- "truncated TLV header");
- }
-
- if ((in[off] & 0xFF) != tag) {
- throw new IllegalArgumentException("unexpected tag 0x" +
- Integer.toHexString(in[off] & 0xFF) + ", expected 0x" +
- Integer.toHexString(tag));
- }
-
- lenByte = in[off + 1] & 0xFF;
-
- if (lenByte < 0x80) {
- length = lenByte;
- contentStart = off + 2;
- }
- else {
- numLenBytes = lenByte & 0x7F;
-
- if (numLenBytes == 0 || numLenBytes > 4) {
- throw new IllegalArgumentException(
- "unsupported DER length form");
- }
-
- if (off + 2 + numLenBytes > limit) {
- throw new IllegalArgumentException(
- "truncated DER length");
- }
-
- /* The first length octet must be non-zero, otherwise the long
- * form carries redundant leading zero octets (non-minimal). */
- if (in[off + 2] == 0x00) {
- throw new IllegalArgumentException(
- "non-minimal DER length (leading zero)");
- }
- length = 0;
-
- for (int i = 0; i < numLenBytes; i++) {
- length = (length << 8) | (in[off + 2 + i] & 0xFF);
- }
-
- if (length < 0x80) {
- throw new IllegalArgumentException(
- "non-minimal DER length");
- }
-
- contentStart = off + 2 + numLenBytes;
- }
-
- /* contentStart <= limit was checked above, so limit - contentStart is
- * non-negative and a crafted ~2GB long-form length cannot overflow
- * the comparison. */
- if ((length < 0) || (length > limit - contentStart)) {
- throw new IllegalArgumentException(
- "DER length exceeds buffer");
- }
-
- return new int[] { contentStart, contentStart + length };
- }
-
- /* Compare a region of 'in' against 'expected'. */
- private static boolean regionEquals(byte[] in, int start, int end,
- byte[] expected) {
-
- if ((end - start) != expected.length) {
- return false;
- }
-
- for (int i = 0; i < expected.length; i++) {
- if (in[start + i] != expected[i]) {
- return false;
- }
- }
-
- return true;
- }
-
- /* Concatenate byte arrays. */
- private static byte[] concat(byte[]... parts) {
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- for (byte[] p : parts) {
- out.write(p, 0, p.length);
- }
-
- return out.toByteArray();
- }
-}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfPQCJdkCompat.java b/src/main/java/com/wolfssl/provider/jce/WolfPQCJdkCompat.java
index af57c4b8..01d912bd 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfPQCJdkCompat.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfPQCJdkCompat.java
@@ -27,6 +27,7 @@
import com.wolfssl.wolfcrypt.MlDsa;
import com.wolfssl.wolfcrypt.MlKem;
+import com.wolfssl.wolfcrypt.SlhDsa;
/**
* JDK reflection helpers for PQC named parameter specs.
@@ -71,6 +72,20 @@ final class WolfPQCJdkCompat {
NPS_ML_DSA_87 = resolveNamedParameterSpec(MlDsa.ML_DSA_87);
}
+ /* Per-parameter-set NamedParameterSpec instances for SLH-DSA, indexed by
+ * the native SlhDsa parameter value (0-11). The JDK ships no SLH-DSA
+ * predefined constants, so on JDK 11+ these are constructed as
+ * NamedParameterSpec(name). null on JDK 8-10. */
+ private static final AlgorithmParameterSpec[] NPS_SLH_DSA =
+ new AlgorithmParameterSpec[12];
+
+ static {
+ for (int p = SlhDsa.SLH_DSA_SHAKE_128S;
+ p <= SlhDsa.SLH_DSA_SHA2_256F; p++) {
+ NPS_SLH_DSA[p] = resolveSlhDsaNamedParamSpec(slhDsaParamToName(p));
+ }
+ }
+
private WolfPQCJdkCompat() { }
/**
@@ -264,4 +279,147 @@ private static AlgorithmParameterSpec resolveNamedParameterSpec(
return null;
}
}
+
+ /**
+ * Map an SLH-DSA parameter set to its canonical FIPS 205 name.
+ *
+ * @param param one of {@code SlhDsa.SLH_DSA_*} (0-11)
+ *
+ * @return canonical name like {@code "SLH-DSA-SHA2-128f"}
+ *
+ * @throws IllegalArgumentException on unknown parameter set
+ */
+ static String slhDsaParamToName(int param) {
+
+ switch (param) {
+ case SlhDsa.SLH_DSA_SHAKE_128S:
+ return "SLH-DSA-SHAKE-128s";
+ case SlhDsa.SLH_DSA_SHAKE_128F:
+ return "SLH-DSA-SHAKE-128f";
+ case SlhDsa.SLH_DSA_SHAKE_192S:
+ return "SLH-DSA-SHAKE-192s";
+ case SlhDsa.SLH_DSA_SHAKE_192F:
+ return "SLH-DSA-SHAKE-192f";
+ case SlhDsa.SLH_DSA_SHAKE_256S:
+ return "SLH-DSA-SHAKE-256s";
+ case SlhDsa.SLH_DSA_SHAKE_256F:
+ return "SLH-DSA-SHAKE-256f";
+ case SlhDsa.SLH_DSA_SHA2_128S:
+ return "SLH-DSA-SHA2-128s";
+ case SlhDsa.SLH_DSA_SHA2_128F:
+ return "SLH-DSA-SHA2-128f";
+ case SlhDsa.SLH_DSA_SHA2_192S:
+ return "SLH-DSA-SHA2-192s";
+ case SlhDsa.SLH_DSA_SHA2_192F:
+ return "SLH-DSA-SHA2-192f";
+ case SlhDsa.SLH_DSA_SHA2_256S:
+ return "SLH-DSA-SHA2-256s";
+ case SlhDsa.SLH_DSA_SHA2_256F:
+ return "SLH-DSA-SHA2-256f";
+ default:
+ throw new IllegalArgumentException(
+ "Invalid SLH-DSA parameter set: " + param);
+ }
+ }
+
+ /**
+ * Map a canonical SLH-DSA parameter-set name (ie:
+ * {@code "SLH-DSA-SHA2-128f"}) to the corresponding {@link SlhDsa}
+ * parameter set constant. Comparison is case-insensitive.
+ *
+ * @param name parameter-set name
+ *
+ * @return one of {@code SlhDsa.SLH_DSA_*} (0-11)
+ *
+ * @throws IllegalArgumentException on unrecognized name
+ */
+ static int slhDsaNameToParam(String name) {
+
+ if (name == null) {
+ throw new IllegalArgumentException("name is null");
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHAKE-128s")) {
+ return SlhDsa.SLH_DSA_SHAKE_128S;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHAKE-128f")) {
+ return SlhDsa.SLH_DSA_SHAKE_128F;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHAKE-192s")) {
+ return SlhDsa.SLH_DSA_SHAKE_192S;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHAKE-192f")) {
+ return SlhDsa.SLH_DSA_SHAKE_192F;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHAKE-256s")) {
+ return SlhDsa.SLH_DSA_SHAKE_256S;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHAKE-256f")) {
+ return SlhDsa.SLH_DSA_SHAKE_256F;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHA2-128s")) {
+ return SlhDsa.SLH_DSA_SHA2_128S;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHA2-128f")) {
+ return SlhDsa.SLH_DSA_SHA2_128F;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHA2-192s")) {
+ return SlhDsa.SLH_DSA_SHA2_192S;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHA2-192f")) {
+ return SlhDsa.SLH_DSA_SHA2_192F;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHA2-256s")) {
+ return SlhDsa.SLH_DSA_SHA2_256S;
+ }
+ if (name.equalsIgnoreCase("SLH-DSA-SHA2-256f")) {
+ return SlhDsa.SLH_DSA_SHA2_256F;
+ }
+ throw new IllegalArgumentException(
+ "Unknown SLH-DSA parameter-set name: " + name);
+ }
+
+ /**
+ * Return the cached {@code NamedParameterSpec} for the given SLH-DSA
+ * parameter set, resolved once at class load.
+ *
+ * @param param one of {@code SlhDsa.SLH_DSA_*} (0-11)
+ *
+ * @return JDK {@code NamedParameterSpec} on JDK 11+, else null
+ *
+ * @throws IllegalArgumentException on unknown parameter set
+ */
+ static AlgorithmParameterSpec slhDsaNamedParameterSpec(int param) {
+
+ if (param < SlhDsa.SLH_DSA_SHAKE_128S ||
+ param > SlhDsa.SLH_DSA_SHA2_256F) {
+ throw new IllegalArgumentException(
+ "Invalid SLH-DSA parameter set: " + param);
+ }
+ return NPS_SLH_DSA[param];
+ }
+
+ /**
+ * Resolve a {@code NamedParameterSpec} for the given SLH-DSA name via
+ * reflection. The JDK ships no SLH-DSA predefined constants, so this only
+ * uses the {@code NamedParameterSpec(String)} constructor path.
+ *
+ * @param name canonical SLH-DSA parameter-set name
+ *
+ * @return JDK {@code NamedParameterSpec} on JDK 11+, else null
+ */
+ private static AlgorithmParameterSpec resolveSlhDsaNamedParamSpec(
+ String name) {
+
+ if (NPS_CLASS == null) {
+ /* Class doesn't exist in JDK 8-10, no spec to return. */
+ return null;
+ }
+
+ try {
+ return (AlgorithmParameterSpec) NPS_CLASS
+ .getConstructor(String.class).newInstance(name);
+ } catch (ReflectiveOperationException e) {
+ return null;
+ }
+ }
}
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfPQCParameterSpec.java b/src/main/java/com/wolfssl/provider/jce/WolfPQCParameterSpec.java
index 63ea042f..77b47e5d 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfPQCParameterSpec.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfPQCParameterSpec.java
@@ -35,7 +35,9 @@
* {@code Signature}/{@code KEM}/{@code KeyPairGenerator}
* algorithm-name conventions used in JDK 24 (JEP 497): {@code "ML-DSA-44"},
* {@code "ML-DSA-65"}, {@code "ML-DSA-87"}, {@code "ML-KEM-512"},
- * {@code "ML-KEM-768"}, {@code "ML-KEM-1024"}.
+ * {@code "ML-KEM-768"}, {@code "ML-KEM-1024"}, and the FIPS 205 SLH-DSA
+ * parameter-set names ({@code "SLH-DSA-SHA2-128s"} ...
+ * {@code "SLH-DSA-SHAKE-256f"}).
*/
public class WolfPQCParameterSpec implements AlgorithmParameterSpec {
@@ -63,6 +65,54 @@ public class WolfPQCParameterSpec implements AlgorithmParameterSpec {
public static final WolfPQCParameterSpec ML_KEM_1024 =
new WolfPQCParameterSpec("ML-KEM-1024");
+ /** SLH-DSA-SHAKE-128s (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHAKE_128S =
+ new WolfPQCParameterSpec("SLH-DSA-SHAKE-128s");
+
+ /** SLH-DSA-SHAKE-128f (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHAKE_128F =
+ new WolfPQCParameterSpec("SLH-DSA-SHAKE-128f");
+
+ /** SLH-DSA-SHAKE-192s (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHAKE_192S =
+ new WolfPQCParameterSpec("SLH-DSA-SHAKE-192s");
+
+ /** SLH-DSA-SHAKE-192f (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHAKE_192F =
+ new WolfPQCParameterSpec("SLH-DSA-SHAKE-192f");
+
+ /** SLH-DSA-SHAKE-256s (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHAKE_256S =
+ new WolfPQCParameterSpec("SLH-DSA-SHAKE-256s");
+
+ /** SLH-DSA-SHAKE-256f (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHAKE_256F =
+ new WolfPQCParameterSpec("SLH-DSA-SHAKE-256f");
+
+ /** SLH-DSA-SHA2-128s (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHA2_128S =
+ new WolfPQCParameterSpec("SLH-DSA-SHA2-128s");
+
+ /** SLH-DSA-SHA2-128f (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHA2_128F =
+ new WolfPQCParameterSpec("SLH-DSA-SHA2-128f");
+
+ /** SLH-DSA-SHA2-192s (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHA2_192S =
+ new WolfPQCParameterSpec("SLH-DSA-SHA2-192s");
+
+ /** SLH-DSA-SHA2-192f (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHA2_192F =
+ new WolfPQCParameterSpec("SLH-DSA-SHA2-192f");
+
+ /** SLH-DSA-SHA2-256s (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHA2_256S =
+ new WolfPQCParameterSpec("SLH-DSA-SHA2-256s");
+
+ /** SLH-DSA-SHA2-256f (FIPS 205). */
+ public static final WolfPQCParameterSpec SLH_DSA_SHA2_256F =
+ new WolfPQCParameterSpec("SLH-DSA-SHA2-256f");
+
private final String name;
/**
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java b/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java
index adbd5502..6b835e90 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java
@@ -71,6 +71,7 @@
import com.wolfssl.wolfcrypt.Aes;
import com.wolfssl.wolfcrypt.FeatureDetect;
import com.wolfssl.wolfcrypt.MlDsa;
+import com.wolfssl.wolfcrypt.SlhDsa;
import com.wolfssl.wolfcrypt.Pwdbased;
import com.wolfssl.wolfcrypt.WolfCrypt;
import com.wolfssl.wolfcrypt.WolfSSLCertManager;
@@ -1126,6 +1127,7 @@ public Key engineGetKey(String alias, char[] password)
PrivateKey pKey = null;
PKCS8EncodedKeySpec p8Spec = null;
KeyFactory keyFact = null;
+ KeyFactory retryFact = null;
SecretKey sKey = null;
@@ -1170,36 +1172,57 @@ public Key engineGetKey(String alias, char[] password)
keyFact = KeyFactory.getInstance("RSASSA-PSS");
} else if (algoId == Asn.ECDSAk) {
keyFact = WolfCryptUtil.getKeyFactoryPreferWolfJCE("EC");
- } else if (algoId != 0 &&
- (algoId == Asn.ML_DSA_LEVEL2k ||
- algoId == Asn.ML_DSA_LEVEL3k ||
- algoId == Asn.ML_DSA_LEVEL5k)) {
+ } else if (isMlDsaAlgoId(algoId)) {
keyFact = getMlDsaKeyFactory();
- } else if (algoId == 0 && FeatureDetect.MlDsaEnabled()) {
+ } else if (isSlhDsaAlgoId(algoId)) {
+ keyFact = getSlhDsaKeyFactory();
+ } else if (algoId == 0 && (FeatureDetect.MlDsaEnabled() ||
+ FeatureDetect.SlhDsaEnabled())) {
/* Fallback for older native wolfSSL, getPkcs8AlgoID
* returned 0 because native ToTraditional_ex() did not
- * recognize ML-DSA OIDs. Let the ML-DSA KeyFactory
- * validate, generatePrivate() below throws for
- * non-ML-DSA DER (avoids a redundant pre-parse of
- * the same PKCS#8). */
- log("using ML-DSA fallback for older native wolfSSL");
- keyFact = getMlDsaKeyFactory();
+ * recognize ML-DSA or SLH-DSA OIDs. Let the PQC KeyFactory
+ * validate, generatePrivate() below throws for non-matching
+ * DER (avoids a redundant pre-parse of the same PKCS#8).
+ * Try ML-DSA first, then retry with SLH-DSA below if that
+ * rejects the encoding. */
+ log("using PQC KeyFactory fallback for older " +
+ "native wolfSSL");
+ if (FeatureDetect.MlDsaEnabled()) {
+ keyFact = getMlDsaKeyFactory();
+ if (FeatureDetect.SlhDsaEnabled()) {
+ retryFact = getSlhDsaKeyFactory();
+ }
+ } else {
+ keyFact = getSlhDsaKeyFactory();
+ }
} else {
throw new NoSuchAlgorithmException(
- "Only RSA, RSASSA-PSS, EC, and ML-DSA private " +
- "key encoding supported: " + algoId);
+ "Only RSA, RSASSA-PSS, EC, ML-DSA, and SLH-DSA " +
+ "private key encoding supported: " + algoId);
}
try {
pKey = keyFact.generatePrivate(p8Spec);
- if (pKey == null) {
- throw new UnrecoverableKeyException(
- "Error generating PrivateKey from " +
- "PKCS8EncodedKeySpec");
- }
} catch (InvalidKeySpecException e) {
+ if (retryFact == null) {
+ throw newUnrecoverableKeyException(
+ "Invalid key spec for KeyFactory", e);
+ }
+ /* ML-DSA KeyFactory rejected DER, try SLH-DSA */
+ try {
+ pKey = retryFact.generatePrivate(p8Spec);
+ } catch (InvalidKeySpecException e2) {
+ /* Keep the ML-DSA rejection visible alongside the
+ * SLH-DSA one from the retry */
+ e2.addSuppressed(e);
+ throw newUnrecoverableKeyException(
+ "Invalid key spec for KeyFactory", e2);
+ }
+ }
+ if (pKey == null) {
throw new UnrecoverableKeyException(
- "Invalid key spec for KeyFactory");
+ "Error generating PrivateKey from " +
+ "PKCS8EncodedKeySpec");
}
}
else if (entry instanceof WKSSecretKey) {
@@ -1497,6 +1520,13 @@ private void checkCertificateChainMatchesPrivateKey(
"trying Java-side match");
match = mlDsaCertMatchesPrivateKey(cert, pkcs8Key);
}
+ if (!match && isSlhDsaAlgorithmName(key.getAlgorithm())) {
+ /* Native X509CheckPrivateKey() / EVP_PKCS82PKEY() cannot
+ * handle SLH-DSA, fall back to a Java-side match. */
+ log("X509CheckPrivateKey returned false for SLH-DSA key, " +
+ "trying Java-side match");
+ match = slhDsaCertMatchesPrivateKey(cert, pkcs8Key);
+ }
if (!match) {
throw new KeyStoreException("X509Certificate does not match " +
"provided private key");
@@ -1623,6 +1653,189 @@ private static KeyFactory getMlDsaKeyFactory()
}
}
+ /**
+ * Java fallback for cert/private key match when native wolfSSL
+ * {@code X509CheckPrivateKey} can't handle SLH-DSA.
+ *
+ * Imports the PKCS#8 private into a wolfJCE SlhDsa, exports the derived
+ * public as X.509 SubjectPublicKeyInfo DER, and compares bytes against the
+ * cert's public key encoding. SLH-DSA SPKI DER is canonical, so byte
+ * equality is safe.
+ *
+ * @param cert the X.509 certificate to match
+ * @param pkcs8Key the PKCS#8-encoded SLH-DSA private key
+ *
+ * @return true if the cert's public key matches the private key
+ */
+ private boolean slhDsaCertMatchesPrivateKey(X509Certificate cert,
+ byte[] pkcs8Key) {
+
+ PublicKey certPub = null;
+ SlhDsa key = null;
+ byte[] certSpki = null;
+ byte[] derivedSpki = null;
+
+ if (!FeatureDetect.SlhDsaEnabled()) {
+ return false;
+ }
+
+ certPub = cert.getPublicKey();
+ if (certPub == null || !isSlhDsaAlgorithmName(certPub.getAlgorithm())) {
+ return false;
+ }
+
+ certSpki = certPub.getEncoded();
+ if (certSpki == null) {
+ return false;
+ }
+
+ /* Remove NULL AlgorithmIdentifier (JDK re-encoding) if present */
+ certSpki = WolfCryptSpkiUtil.stripNullAlgIdParams(certSpki);
+
+ try {
+ /* Parameter set auto-detected from the PKCS#8 OID. */
+ key = new SlhDsa();
+ key.importPrivateKeyDer(pkcs8Key);
+ derivedSpki = key.exportPublicKeyDer(true);
+
+ return Arrays.equals(certSpki, derivedSpki);
+ }
+ catch (WolfCryptException e) {
+ return false;
+ }
+ finally {
+ if (key != null) {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ /**
+ * Check if a JCA algorithm name identifies SLH-DSA.
+ *
+ * Accepts the generic name, the FIPS 205 parameter-set names, and the
+ * raw OID strings that CertificateFactory implementations may return
+ * from getAlgorithm() for algorithms they do not recognize.
+ *
+ * @param algo algorithm name from Key.getAlgorithm(), may be null
+ *
+ * @return true if the name identifies an SLH-DSA key, otherwise false
+ */
+ private static boolean isSlhDsaAlgorithmName(String algo) {
+
+ if (algo == null) {
+ return false;
+ }
+
+ if (algo.equalsIgnoreCase("SLH-DSA")) {
+ return true;
+ }
+
+ /* Per-set names SLH-DSA-SHA2-* and SLH-DSA-SHAKE-*. */
+ if (algo.regionMatches(true, 0, "SLH-DSA-", 0, 8)) {
+ return true;
+ }
+
+ /* OID aliases 2.16.840.1.101.3.4.3.20 - .31. */
+ if (algo.startsWith("2.16.840.1.101.3.4.3.")) {
+ try {
+ int last = Integer.parseInt(
+ algo.substring("2.16.840.1.101.3.4.3.".length()));
+ return last >= 20 && last <= 31;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get a KeyFactory for SLH-DSA, preferring the wolfJCE provider.
+ *
+ * @return KeyFactory for SLH-DSA
+ *
+ * @throws NoSuchAlgorithmException if no provider supports SLH-DSA
+ */
+ private static KeyFactory getSlhDsaKeyFactory()
+ throws NoSuchAlgorithmException {
+
+ try {
+ return KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+ } catch (NoSuchProviderException | NoSuchAlgorithmException e) {
+ /* wolfJCE not registered by name (or built without SLH-DSA) */
+ return KeyFactory.getInstance("SLH-DSA");
+ }
+ }
+
+ /**
+ * Create an UnrecoverableKeyException with a cause attached.
+ * UnrecoverableKeyException has no (String, Throwable) constructor,
+ * so the cause is chained via initCause().
+ *
+ * @param msg exception message
+ * @param cause underlying exception to preserve
+ *
+ * @return new UnrecoverableKeyException with cause set
+ */
+ private static UnrecoverableKeyException newUnrecoverableKeyException(
+ String msg, Throwable cause) {
+
+ UnrecoverableKeyException e = new UnrecoverableKeyException(msg);
+ e.initCause(cause);
+ return e;
+ }
+
+ /**
+ * Return true if the given native Key_Sum (the PKCS#8 AlgorithmIdentifier
+ * OID sum returned by Asn.getPkcs8AlgoID()) identifies one of the three
+ * FIPS 204 ML-DSA parameter sets.
+ *
+ * The algoId != 0 guard matters: on a native build without ML-DSA the
+ * Asn.ML_DSA_LEVEL*k constants resolve to 0, so an unrelated key whose
+ * algoId is 0 must not be misidentified as ML-DSA.
+ *
+ * @param algoId native Key_Sum value from Asn.getPkcs8AlgoID()
+ *
+ * @return true if algoId is one of the ML-DSA parameter-set OID sums
+ */
+ private static boolean isMlDsaAlgoId(int algoId) {
+ return algoId != 0 &&
+ (algoId == Asn.ML_DSA_LEVEL2k ||
+ algoId == Asn.ML_DSA_LEVEL3k ||
+ algoId == Asn.ML_DSA_LEVEL5k);
+ }
+
+ /**
+ * Return true if the given native Key_Sum (the PKCS#8 AlgorithmIdentifier
+ * OID sum returned by Asn.getPkcs8AlgoID()) identifies one of the FIPS 205
+ * SLH-DSA parameter sets.
+ *
+ * On a native build without SLH-DSA Asn.SLH_DSA_*k constants resolve to 0,
+ * so an unrelated key whose algoId is 0 must not be misidentified as
+ * SLH-DSA.
+ *
+ * @param algoId native Key_Sum value from Asn.getPkcs8AlgoID()
+ *
+ * @return true if algoId is one of the SLH-DSA parameter-set OID sums
+ */
+ private static boolean isSlhDsaAlgoId(int algoId) {
+
+ return algoId != 0 &&
+ (algoId == Asn.SLH_DSA_SHA2_128Sk ||
+ algoId == Asn.SLH_DSA_SHA2_128Fk ||
+ algoId == Asn.SLH_DSA_SHA2_192Sk ||
+ algoId == Asn.SLH_DSA_SHA2_192Fk ||
+ algoId == Asn.SLH_DSA_SHA2_256Sk ||
+ algoId == Asn.SLH_DSA_SHA2_256Fk ||
+ algoId == Asn.SLH_DSA_SHAKE_128Sk ||
+ algoId == Asn.SLH_DSA_SHAKE_128Fk ||
+ algoId == Asn.SLH_DSA_SHAKE_192Sk ||
+ algoId == Asn.SLH_DSA_SHAKE_192Fk ||
+ algoId == Asn.SLH_DSA_SHAKE_256Sk ||
+ algoId == Asn.SLH_DSA_SHAKE_256Fk);
+ }
+
/**
* Assign the given key to an alias and protects it using the
* provided password.
diff --git a/src/main/java/com/wolfssl/wolfcrypt/Asn.java b/src/main/java/com/wolfssl/wolfcrypt/Asn.java
index f816a790..a90a89cb 100644
--- a/src/main/java/com/wolfssl/wolfcrypt/Asn.java
+++ b/src/main/java/com/wolfssl/wolfcrypt/Asn.java
@@ -58,6 +58,42 @@ public Asn() {
/** ML-DSA-87 (FIPS 204) key value, from oid_sum.h Key_Sum enum.
* OID 2.16.840.1.101.3.4.3.19. */
public static final int ML_DSA_LEVEL5k;
+ /** SLH-DSA-SHA2-128s (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.20. */
+ public static final int SLH_DSA_SHA2_128Sk;
+ /** SLH-DSA-SHA2-128f (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.21. */
+ public static final int SLH_DSA_SHA2_128Fk;
+ /** SLH-DSA-SHA2-192s (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.22. */
+ public static final int SLH_DSA_SHA2_192Sk;
+ /** SLH-DSA-SHA2-192f (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.23. */
+ public static final int SLH_DSA_SHA2_192Fk;
+ /** SLH-DSA-SHA2-256s (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.24. */
+ public static final int SLH_DSA_SHA2_256Sk;
+ /** SLH-DSA-SHA2-256f (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.25. */
+ public static final int SLH_DSA_SHA2_256Fk;
+ /** SLH-DSA-SHAKE-128s (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.26. */
+ public static final int SLH_DSA_SHAKE_128Sk;
+ /** SLH-DSA-SHAKE-128f (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.27. */
+ public static final int SLH_DSA_SHAKE_128Fk;
+ /** SLH-DSA-SHAKE-192s (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.28. */
+ public static final int SLH_DSA_SHAKE_192Sk;
+ /** SLH-DSA-SHAKE-192f (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.29. */
+ public static final int SLH_DSA_SHAKE_192Fk;
+ /** SLH-DSA-SHAKE-256s (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.30. */
+ public static final int SLH_DSA_SHAKE_256Sk;
+ /** SLH-DSA-SHAKE-256f (FIPS 205) key value, from oid_sum.h Key_Sum enum.
+ * OID 2.16.840.1.101.3.4.3.31. */
+ public static final int SLH_DSA_SHAKE_256Fk;
/* Hash Sum values, from oid_sum.h Hash_Sum enum */
@@ -91,6 +127,18 @@ public Asn() {
ML_DSA_LEVEL2k = getML_DSA_LEVEL2k();
ML_DSA_LEVEL3k = getML_DSA_LEVEL3k();
ML_DSA_LEVEL5k = getML_DSA_LEVEL5k();
+ SLH_DSA_SHA2_128Sk = getSLH_DSA_SHA2_128Sk();
+ SLH_DSA_SHA2_128Fk = getSLH_DSA_SHA2_128Fk();
+ SLH_DSA_SHA2_192Sk = getSLH_DSA_SHA2_192Sk();
+ SLH_DSA_SHA2_192Fk = getSLH_DSA_SHA2_192Fk();
+ SLH_DSA_SHA2_256Sk = getSLH_DSA_SHA2_256Sk();
+ SLH_DSA_SHA2_256Fk = getSLH_DSA_SHA2_256Fk();
+ SLH_DSA_SHAKE_128Sk = getSLH_DSA_SHAKE_128Sk();
+ SLH_DSA_SHAKE_128Fk = getSLH_DSA_SHAKE_128Fk();
+ SLH_DSA_SHAKE_192Sk = getSLH_DSA_SHAKE_192Sk();
+ SLH_DSA_SHAKE_192Fk = getSLH_DSA_SHAKE_192Fk();
+ SLH_DSA_SHAKE_256Sk = getSLH_DSA_SHAKE_256Sk();
+ SLH_DSA_SHAKE_256Fk = getSLH_DSA_SHAKE_256Fk();
MD5h = getMD5h();
SHAh = getSHAh();
@@ -128,6 +176,42 @@ public Asn() {
/** Return value of native ML_DSA_LEVEL5k enum */
private static native int getML_DSA_LEVEL5k();
+ /** Return value of native SLH_DSA_SHA2_128Sk enum */
+ private static native int getSLH_DSA_SHA2_128Sk();
+
+ /** Return value of native SLH_DSA_SHA2_128Fk enum */
+ private static native int getSLH_DSA_SHA2_128Fk();
+
+ /** Return value of native SLH_DSA_SHA2_192Sk enum */
+ private static native int getSLH_DSA_SHA2_192Sk();
+
+ /** Return value of native SLH_DSA_SHA2_192Fk enum */
+ private static native int getSLH_DSA_SHA2_192Fk();
+
+ /** Return value of native SLH_DSA_SHA2_256Sk enum */
+ private static native int getSLH_DSA_SHA2_256Sk();
+
+ /** Return value of native SLH_DSA_SHA2_256Fk enum */
+ private static native int getSLH_DSA_SHA2_256Fk();
+
+ /** Return value of native SLH_DSA_SHAKE_128Sk enum */
+ private static native int getSLH_DSA_SHAKE_128Sk();
+
+ /** Return value of native SLH_DSA_SHAKE_128Fk enum */
+ private static native int getSLH_DSA_SHAKE_128Fk();
+
+ /** Return value of native SLH_DSA_SHAKE_192Sk enum */
+ private static native int getSLH_DSA_SHAKE_192Sk();
+
+ /** Return value of native SLH_DSA_SHAKE_192Fk enum */
+ private static native int getSLH_DSA_SHAKE_192Fk();
+
+ /** Return value of native SLH_DSA_SHAKE_256Sk enum */
+ private static native int getSLH_DSA_SHAKE_256Sk();
+
+ /** Return value of native SLH_DSA_SHAKE_256Fk enum */
+ private static native int getSLH_DSA_SHAKE_256Fk();
+
/** Return value of native MD5h enum */
private static native int getMD5h();
diff --git a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java
index c9d7baf9..ba4e17cb 100644
--- a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java
+++ b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java
@@ -408,6 +408,16 @@ public class FeatureDetect {
*/
public static native boolean SlhDsaEnabled();
+ /**
+ * Tests if SLH-DSA (FIPS 205) key generation and signing are compiled
+ * into the native wolfSSL library. False on verify-only native builds.
+ *
+ * @return true if enabled (WOLFSSL_HAVE_SLHDSA without
+ * WOLFSSL_SLHDSA_VERIFY_ONLY), otherwise false if not
+ * compiled in.
+ */
+ public static native boolean SlhDsaKeyGenEnabled();
+
/**
* Tests if LMS/HSS (RFC 8554) is compiled into the native wolfSSL
* library.
diff --git a/src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java b/src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java
new file mode 100644
index 00000000..0e90ad14
--- /dev/null
+++ b/src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java
@@ -0,0 +1,1092 @@
+/* SlhDsa.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.wolfcrypt;
+
+/**
+ * Wrapper for the native WolfCrypt SLH-DSA (FIPS 205) implementation.
+ *
+ * Mirrors the native wolfCrypt {@code wc_SlhDsaKey_*} API. SLH-DSA is a
+ * stateless hash-based signature scheme (formerly SPHINCS+). The parameter
+ * set is fixed at construction and maps directly to the native
+ * {@code enum SlhDsaParam} integer value. An empty FIPS 205 context is used
+ * by default.
+ *
+ * Parameter set constants match the native {@code enum SlhDsaParam}: the
+ * six SHAKE sets are 0-5, the six SHA2 sets are 6-11. Note this ordering is
+ * the reverse of the NIST OID ordering (SHA2 OIDs come before SHAKE).
+ */
+public class SlhDsa extends NativeStruct {
+
+ /** SLH-DSA-SHAKE-128s parameter set, NIST security category 1. */
+ public static final int SLH_DSA_SHAKE_128S = 0;
+
+ /** SLH-DSA-SHAKE-128f parameter set, NIST security category 1. */
+ public static final int SLH_DSA_SHAKE_128F = 1;
+
+ /** SLH-DSA-SHAKE-192s parameter set, NIST security category 3. */
+ public static final int SLH_DSA_SHAKE_192S = 2;
+
+ /** SLH-DSA-SHAKE-192f parameter set, NIST security category 3. */
+ public static final int SLH_DSA_SHAKE_192F = 3;
+
+ /** SLH-DSA-SHAKE-256s parameter set, NIST security category 5. */
+ public static final int SLH_DSA_SHAKE_256S = 4;
+
+ /** SLH-DSA-SHAKE-256f parameter set, NIST security category 5. */
+ public static final int SLH_DSA_SHAKE_256F = 5;
+
+ /** SLH-DSA-SHA2-128s parameter set, NIST security category 1. */
+ public static final int SLH_DSA_SHA2_128S = 6;
+
+ /** SLH-DSA-SHA2-128f parameter set, NIST security category 1. */
+ public static final int SLH_DSA_SHA2_128F = 7;
+
+ /** SLH-DSA-SHA2-192s parameter set, NIST security category 3. */
+ public static final int SLH_DSA_SHA2_192S = 8;
+
+ /** SLH-DSA-SHA2-192f parameter set, NIST security category 3. */
+ public static final int SLH_DSA_SHA2_192F = 9;
+
+ /** SLH-DSA-SHA2-256s parameter set, NIST security category 5. */
+ public static final int SLH_DSA_SHA2_256S = 10;
+
+ /** SLH-DSA-SHA2-256f parameter set, NIST security category 5. */
+ public static final int SLH_DSA_SHA2_256F = 11;
+
+ /** FIPS 205 maximum context length, in bytes. */
+ public static final int SLH_DSA_MAX_CONTEXT_LEN = 255;
+
+ /* Lowest and highest valid native enum SlhDsaParam values. */
+ private static final int PARAM_MIN = 0;
+ private static final int PARAM_MAX = 11;
+
+ /* Used by no-arg constructor before DER import detects the param set. */
+ private static final int PARAM_UNSET = -1;
+
+ private WolfCryptState state = WolfCryptState.UNINITIALIZED;
+
+ /** Lock around object state. */
+ protected final Object stateLock = new Object();
+
+ /** SLH-DSA parameter set. Volatile so {@link #getParam()} sees the latest
+ * value after the auto-detect refresh inside the DER import paths. */
+ private volatile int param;
+
+ /**
+ * Create a new SLH-DSA object for the given parameter set.
+ *
+ * @param param one of the {@code SLH_DSA_*} parameter set constants
+ * (0-11, matching native {@code enum SlhDsaParam}).
+ *
+ * @throws WolfCryptException if SLH-DSA is not compiled into native
+ * wolfCrypt or {@code param} is not a valid parameter set.
+ */
+ public SlhDsa(int param) throws WolfCryptException {
+
+ if (!FeatureDetect.SlhDsaEnabled()) {
+ throw new WolfCryptException(
+ WolfCryptError.NOT_COMPILED_IN.getCode());
+ }
+
+ if (param < PARAM_MIN || param > PARAM_MAX) {
+ throw new WolfCryptException(WolfCryptError.BAD_FUNC_ARG.getCode());
+ }
+
+ this.param = param;
+ /* Native struct lazy init in checkStateAndInitialize() */
+ }
+
+ /**
+ * Create a new SLH-DSA object with the parameter set deferred. The
+ * parameter set is auto-detected on the first
+ * {@link #importPublicKeyDer(byte[])} or
+ * {@link #importPrivateKeyDer(byte[])} call from the AlgorithmIdentifier
+ * OID.
+ *
+ * Cannot be used with {@link #makeKey(Rng)},
+ * {@link #importPublicKey(byte[])}, or {@link #importPrivateKey(byte[])}.
+ * Those paths require the parameter set to be set up front, since raw key
+ * bytes do not carry it (and several sets share the same byte sizes).
+ *
+ * @throws WolfCryptException if SLH-DSA not compiled into native wolfCrypt.
+ */
+ public SlhDsa() throws WolfCryptException {
+
+ if (!FeatureDetect.SlhDsaEnabled()) {
+ throw new WolfCryptException(
+ WolfCryptError.NOT_COMPILED_IN.getCode());
+ }
+
+ this.param = PARAM_UNSET;
+ /* Native struct lazy init in checkStateAndInitialize(). Parameter set
+ * is detected later by DER import. */
+ }
+
+ @Override
+ public void releaseNativeStruct() {
+ synchronized (stateLock) {
+ if ((state != WolfCryptState.UNINITIALIZED) &&
+ (state != WolfCryptState.RELEASED)) {
+
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_free();
+ }
+ super.releaseNativeStruct();
+ state = WolfCryptState.RELEASED;
+ }
+ }
+ }
+
+ /**
+ * Allocate native SlhDsaKey struct.
+ *
+ * @return native allocated pointer
+ *
+ * @throws OutOfMemoryError when malloc fails
+ */
+ protected native long mallocNativeStruct() throws OutOfMemoryError;
+
+ private native void wc_SlhDsaKey_init(int param);
+ private native void wc_SlhDsaKey_free();
+ private native int wc_SlhDsaKey_get_param();
+ private native void wc_SlhDsaKey_make_key(Rng rng);
+ private native void wc_SlhDsaKey_make_key_with_seeds(byte[] skSeed,
+ byte[] skPrf, byte[] pkSeed);
+ private native byte[] wc_SlhDsaKey_sign(byte[] ctx, byte[] msg, Rng rng);
+ private native byte[] wc_SlhDsaKey_sign_deterministic(byte[] ctx,
+ byte[] msg);
+ private native byte[] wc_SlhDsaKey_sign_hash(byte[] ctx, int hashAlg,
+ byte[] hash, Rng rng);
+ private native byte[] wc_SlhDsaKey_sign_msg_prehash(byte[] ctx, byte[] msg,
+ Rng rng);
+ private native boolean wc_SlhDsaKey_verify(byte[] sig, byte[] ctx,
+ byte[] msg);
+ private native boolean wc_SlhDsaKey_verify_hash(byte[] sig, byte[] ctx,
+ int hashAlg, byte[] hash);
+ private native boolean wc_SlhDsaKey_verify_msg_prehash(byte[] sig,
+ byte[] ctx, byte[] msg);
+ private native byte[] wc_SlhDsaKey_export_public();
+ private native byte[] wc_SlhDsaKey_export_private();
+ private native void wc_SlhDsaKey_import_public(byte[] in);
+ private native void wc_SlhDsaKey_import_private(byte[] in);
+ private native byte[] wc_SlhDsaKey_PublicKeyToDer(boolean withAlg);
+ private native byte[] wc_SlhDsaKey_KeyToDer();
+ private native void wc_SlhDsaKey_PublicKeyDecode(byte[] der);
+ private native void wc_SlhDsaKey_PrivateKeyDecode(byte[] der);
+ private native int wc_SlhDsaKey_pub_size();
+ private native int wc_SlhDsaKey_priv_size();
+ private native int wc_SlhDsaKey_sig_size();
+ private native void wc_SlhDsaKey_check_key();
+
+ /**
+ * Allocate, initialize, and set the parameter set. State advances
+ * UNINITIALIZED to INITIALIZED on success.
+ *
+ * @throws IllegalStateException if releaseNativeStruct() has been called
+ * or initialization fails
+ */
+ private synchronized void checkStateAndInitialize()
+ throws IllegalStateException {
+
+ synchronized (stateLock) {
+ if (state == WolfCryptState.RELEASED) {
+ throw new IllegalStateException("Object has been released");
+ }
+
+ if (state == WolfCryptState.UNINITIALIZED) {
+ synchronized (pointerLock) {
+ initNativeStruct();
+ try {
+ if (this.param != PARAM_UNSET) {
+ wc_SlhDsaKey_init(this.param);
+ }
+ else {
+ /* No-arg object, init placeholder. Real set is
+ * detected later from DER import. */
+ initDeferredPlaceholder();
+ }
+ } catch (WolfCryptException e) {
+ /* Init failed for every candidate parameter set.
+ * The struct was already allocated, free it since
+ * state stays UNINITIALIZED and releaseNativeStruct()
+ * would otherwise skip it. */
+ super.releaseNativeStruct();
+ throw e;
+ }
+ }
+ state = WolfCryptState.INITIALIZED;
+ }
+ }
+ }
+
+ /**
+ * Native initialize the already allocated struct with the first
+ * compiled-in parameter set, for a deferred (no-arg) object whose real
+ * parameter set is detected later from DER import OID. Caller holds
+ * pointerLock. The native struct is reused across attempts. Native Init
+ * validates the parameter set before touching the struct, so retrying a
+ * different set after a failure is safe.
+ *
+ * @throws WolfCryptException if no parameter set is compiled in
+ */
+ private void initDeferredPlaceholder() throws WolfCryptException {
+
+ WolfCryptException lastErr = null;
+
+ for (int p = PARAM_MIN; p <= PARAM_MAX; p++) {
+ try {
+ wc_SlhDsaKey_init(p);
+ return;
+
+ } catch (WolfCryptException e) {
+ lastErr = e;
+ }
+ }
+
+ if (lastErr != null) {
+ throw lastErr;
+ }
+
+ throw new WolfCryptException(WolfCryptError.NOT_COMPILED_IN.getCode());
+ }
+
+ /**
+ * Throw exception if this object already has a key loaded.
+ *
+ * @throws IllegalStateException if state is READY (key loaded)
+ */
+ private void throwIfKeyExists() throws IllegalStateException {
+
+ synchronized (stateLock) {
+ if (state == WolfCryptState.READY) {
+ throw new IllegalStateException("Object already has a key");
+ }
+ }
+ }
+
+ /**
+ * Throw exception if this object does not have a key loaded.
+ *
+ * @throws IllegalStateException if state is not READY (no key loaded)
+ */
+ private void throwIfKeyNotLoaded() throws IllegalStateException {
+
+ synchronized (stateLock) {
+ if (state != WolfCryptState.READY) {
+ throw new IllegalStateException(
+ "No key available to perform the operation");
+ }
+ }
+ }
+
+ /**
+ * Reject operations that require the parameter set up front (raw imports,
+ * keygen) when this object was created via the no-arg constructor and has
+ * not yet auto-detected via a DER import.
+ *
+ * @throws WolfCryptException if the parameter set is not set
+ */
+ private void requireParamSet() throws WolfCryptException {
+
+ if (this.param == PARAM_UNSET) {
+ throw new WolfCryptException(
+ WolfCryptError.BAD_FUNC_ARG.getCode());
+ }
+ }
+
+ /**
+ * Throw exception if FIPS 205 context exceeds maximum length.
+ *
+ * @param ctx context bytes, may be null
+ *
+ * @throws IllegalArgumentException if ctx length exceeds 255
+ */
+ private static void checkCtxLength(byte[] ctx)
+ throws IllegalArgumentException {
+
+ if (ctx != null && ctx.length > SLH_DSA_MAX_CONTEXT_LEN) {
+ throw new IllegalArgumentException(
+ "SLH-DSA context length exceeds 255 bytes");
+ }
+ }
+
+ /**
+ * Get the security parameter n, in bytes, for an SLH-DSA parameter set.
+ *
+ * @param param SLH-DSA parameter set (0-11)
+ *
+ * @return n in bytes (16, 24, or 32)
+ *
+ * @throws WolfCryptException if {@code param} is not a valid parameter
+ * set (including {@code -1} from {@link #getParam()} on a no-arg
+ * object before DER import)
+ */
+ public static int paramToN(int param) throws WolfCryptException {
+
+ if (param < PARAM_MIN || param > PARAM_MAX) {
+ throw new WolfCryptException(WolfCryptError.BAD_FUNC_ARG.getCode());
+ }
+
+ /* Both the SHAKE (0-5) and SHA2 (6-11) blocks order the sets as
+ * 128s,128f,192s,192f,256s,256f, so (param % 6) / 2 gives the
+ * category index 0=128, 1=192, 2=256 and n = 16 + 8 * index. */
+ return 16 + 8 * ((param % 6) / 2);
+ }
+
+ /**
+ * Get the parameter set selected for this object.
+ *
+ * @return one of the {@code SLH_DSA_*} constants (0-11), or {@code -1} if
+ * the no-arg constructor was used and no DER has been imported yet.
+ */
+ public int getParam() {
+ return this.param;
+ }
+
+ /**
+ * Generate an SLH-DSA key pair for this object's parameter set.
+ *
+ * @param rng initialized {@link Rng}
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if a key has already been loaded or the
+ * object has been released
+ */
+ public void makeKey(Rng rng)
+ throws WolfCryptException, IllegalStateException {
+
+ requireParamSet();
+ checkStateAndInitialize();
+ throwIfKeyExists();
+
+ synchronized (stateLock) {
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_make_key(rng);
+ }
+ state = WolfCryptState.READY;
+ }
+ }
+
+ /**
+ * Generate an SLH-DSA key pair deterministically from the three secret
+ * seeds, implements FIPS 205 SLH-DSA.KeyGen_internal. Mainly useful for
+ * known answer tests, general key generation should use
+ * {@link #makeKey(Rng)}.
+ *
+ * @param skSeed SK.seed value, must be {@code paramToN(param)} bytes
+ * @param skPrf SK.prf value, must be {@code paramToN(param)} bytes
+ * @param pkSeed PK.seed value, must be {@code paramToN(param)} bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if any seed is null or not the expected
+ * length
+ * @throws IllegalStateException if a key has already been loaded or the
+ * object has been released
+ */
+ public void makeKeyWithSeeds(byte[] skSeed, byte[] skPrf, byte[] pkSeed)
+ throws WolfCryptException, IllegalStateException {
+
+ requireParamSet();
+
+ int n = paramToN(this.param);
+ if (skSeed == null || skSeed.length != n ||
+ skPrf == null || skPrf.length != n ||
+ pkSeed == null || pkSeed.length != n) {
+ throw new IllegalArgumentException(
+ "SLH-DSA keygen seeds must each be " + n + " bytes");
+ }
+
+ checkStateAndInitialize();
+ throwIfKeyExists();
+
+ synchronized (stateLock) {
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_make_key_with_seeds(skSeed, skPrf, pkSeed);
+ }
+ state = WolfCryptState.READY;
+ }
+ }
+
+ /**
+ * Sign {@code msg} with an empty FIPS 205 context.
+ *
+ * @param msg message to sign (may be empty, must not be null)
+ * @param rng initialized {@link Rng}
+ *
+ * @return signature bytes, length matches {@link #signatureSize()}
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] sign(byte[] msg, Rng rng)
+ throws WolfCryptException, IllegalStateException {
+
+ return sign(msg, null, rng);
+ }
+
+ /**
+ * Sign {@code msg} with the given FIPS 205 context.
+ *
+ * @param msg message to sign (may be empty, must not be null)
+ * @param ctx context bytes (may be null or empty for an empty context;
+ * length must be 0..255 per FIPS 205)
+ * @param rng initialized {@link Rng}
+ *
+ * @return signature bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if {@code ctx} length exceeds 255
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] sign(byte[] msg, byte[] ctx, Rng rng)
+ throws WolfCryptException, IllegalStateException {
+
+ checkCtxLength(ctx);
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_sign(ctx, msg, rng);
+ }
+ }
+
+ /**
+ * Sign {@code msg} deterministically with the given FIPS 205 context.
+ * Mainly useful for known answer tests, general signing should use
+ * {@link #sign(byte[], byte[], Rng)}.
+ *
+ * @param msg message to sign (may be empty, must not be null)
+ * @param ctx context bytes (may be null or empty for an empty context;
+ * length must be 0..255 per FIPS 205)
+ *
+ * @return signature bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if {@code ctx} length exceeds 255
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] signDeterministic(byte[] msg, byte[] ctx)
+ throws WolfCryptException, IllegalStateException {
+
+ checkCtxLength(ctx);
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_sign_deterministic(ctx, msg);
+ }
+ }
+
+ /**
+ * Sign a message digest with an empty FIPS 205 context, implements
+ * HashSLH-DSA (pre-hash variant) from FIPS 205 Section 10.2.2.
+ *
+ * @param hash digest of the message to sign
+ * @param hashAlg hash algorithm used to compute {@code hash}, one of the
+ * {@code WolfCrypt.WC_HASH_TYPE_*} values
+ * @param rng initialized {@link Rng}
+ *
+ * @return signature bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] signHash(byte[] hash, int hashAlg, Rng rng)
+ throws WolfCryptException, IllegalStateException {
+
+ return signHash(hash, hashAlg, null, rng);
+ }
+
+ /**
+ * Sign a message digest with the given FIPS 205 context, implements
+ * HashSLH-DSA (pre-hash variant) from FIPS 205 Section 10.2.2.
+ *
+ * @param hash digest of the message to sign
+ * @param hashAlg hash algorithm used to compute {@code hash}, one of the
+ * {@code WolfCrypt.WC_HASH_TYPE_*} values
+ * @param ctx context bytes (may be null or empty for an empty context;
+ * length must be 0..255 per FIPS 205)
+ * @param rng initialized {@link Rng}
+ *
+ * @return signature bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if {@code ctx} length exceeds 255
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] signHash(byte[] hash, int hashAlg, byte[] ctx, Rng rng)
+ throws WolfCryptException, IllegalStateException {
+
+ checkCtxLength(ctx);
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_sign_hash(ctx, hashAlg, hash, rng);
+ }
+ }
+
+ /**
+ * Verify {@code sig} over {@code msg} with an empty context.
+ *
+ * @param sig signature to verify
+ * @param msg message bytes
+ *
+ * @return true if signature verifies, false otherwise
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public boolean verify(byte[] sig, byte[] msg)
+ throws WolfCryptException, IllegalStateException {
+
+ return verify(sig, msg, null);
+ }
+
+ /**
+ * Verify {@code sig} over {@code msg} with the given context.
+ *
+ * @param sig signature to verify
+ * @param msg message bytes
+ * @param ctx context bytes (may be null or empty; length 0..255)
+ *
+ * @return true if signature verifies, false otherwise
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if {@code ctx} length exceeds 255
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public boolean verify(byte[] sig, byte[] msg, byte[] ctx)
+ throws WolfCryptException, IllegalStateException {
+
+ checkCtxLength(ctx);
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_verify(sig, ctx, msg);
+ }
+ }
+
+ /**
+ * Sign {@code msg} using HashSLH-DSA (pre-hash variant) from FIPS 205
+ * Section 10.2.2 with an empty context.
+ *
+ * The message is hashed with the parameter set's standardized pre-hash
+ * function (SHA-256 for SHA2-128, SHA-512 for SHA2-192/256, SHAKE128 for
+ * SHAKE-128, SHAKE256 for SHAKE-192/256) and the resulting digest is signed
+ * as HashSLH-DSA. The hashing is performed natively.
+ *
+ * @param msg message to sign (may be empty, must not be null)
+ * @param rng initialized {@link Rng}
+ *
+ * @return signature bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] signPreHash(byte[] msg, Rng rng)
+ throws WolfCryptException, IllegalStateException {
+
+ return signPreHash(msg, null, rng);
+ }
+
+ /**
+ * Sign {@code msg} using HashSLH-DSA (pre-hash variant) from FIPS 205
+ * Section 10.2.2 with the given context.
+ *
+ * The message is hashed with the parameter set's standardized pre-hash
+ * function and the resulting digest is signed as HashSLH-DSA. The hashing
+ * is performed natively.
+ *
+ * @param msg message to sign (may be empty, must not be null)
+ * @param ctx context bytes (may be null or empty for an empty context;
+ * length must be 0..255 per FIPS 205)
+ * @param rng initialized {@link Rng}
+ *
+ * @return signature bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if {@code ctx} length exceeds 255
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] signPreHash(byte[] msg, byte[] ctx, Rng rng)
+ throws WolfCryptException, IllegalStateException {
+
+ checkCtxLength(ctx);
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_sign_msg_prehash(ctx, msg, rng);
+ }
+ }
+
+ /**
+ * Verify {@code sig} over {@code msg} using HashSLH-DSA (pre-hash variant)
+ * from FIPS 205 Section 10.2.2 with an empty context.
+ *
+ * @param sig signature to verify
+ * @param msg message bytes
+ *
+ * @return true if signature verifies, false otherwise
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public boolean verifyPreHash(byte[] sig, byte[] msg)
+ throws WolfCryptException, IllegalStateException {
+
+ return verifyPreHash(sig, msg, null);
+ }
+
+ /**
+ * Verify {@code sig} over {@code msg} using HashSLH-DSA (pre-hash variant)
+ * from FIPS 205 Section 10.2.2 with the given context.
+ *
+ * @param sig signature to verify
+ * @param msg message bytes
+ * @param ctx context bytes (may be null or empty; length 0..255)
+ *
+ * @return true if signature verifies, false otherwise
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if {@code ctx} length exceeds 255
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public boolean verifyPreHash(byte[] sig, byte[] msg, byte[] ctx)
+ throws WolfCryptException, IllegalStateException {
+
+ checkCtxLength(ctx);
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_verify_msg_prehash(sig, ctx, msg);
+ }
+ }
+
+ /**
+ * Verify {@code sig} over a message digest with an empty context,
+ * implements HashSLH-DSA (pre-hash variant) from FIPS 205 Section 10.2.2.
+ *
+ * @param sig signature to verify
+ * @param hash digest of the message that was signed
+ * @param hashAlg hash algorithm used to compute {@code hash}, one of the
+ * {@code WolfCrypt.WC_HASH_TYPE_*} values
+ *
+ * @return true if signature verifies, false otherwise
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public boolean verifyHash(byte[] sig, byte[] hash, int hashAlg)
+ throws WolfCryptException, IllegalStateException {
+
+ return verifyHash(sig, hash, hashAlg, null);
+ }
+
+ /**
+ * Verify {@code sig} over a message digest with the given context,
+ * implements HashSLH-DSA (pre-hash variant) from FIPS 205 Section 10.2.2.
+ *
+ * @param sig signature to verify
+ * @param hash digest of the message that was signed
+ * @param hashAlg hash algorithm used to compute {@code hash}, one of the
+ * {@code WolfCrypt.WC_HASH_TYPE_*} values
+ * @param ctx context bytes (may be null or empty; length 0..255)
+ *
+ * @return true if signature verifies, false otherwise
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalArgumentException if {@code ctx} length exceeds 255
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public boolean verifyHash(byte[] sig, byte[] hash, int hashAlg, byte[] ctx)
+ throws WolfCryptException, IllegalStateException {
+
+ checkCtxLength(ctx);
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_verify_hash(sig, ctx, hashAlg, hash);
+ }
+ }
+
+ /**
+ * Export raw public key bytes for this object's SLH-DSA parameter set.
+ *
+ * @return public key as a byte array (2n bytes)
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] exportPublicKey()
+ throws WolfCryptException, IllegalStateException {
+
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_export_public();
+ }
+ }
+
+ /**
+ * Export raw private key bytes for this object's SLH-DSA parameter set.
+ *
+ * @return private key as a byte array (4n bytes)
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] exportPrivateKey()
+ throws WolfCryptException, IllegalStateException {
+
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_export_private();
+ }
+ }
+
+ /**
+ * Import a raw SLH-DSA public key matching this object's parameter set.
+ *
+ * @param in raw public key bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if a key is already loaded or object
+ * released
+ */
+ public void importPublicKey(byte[] in)
+ throws WolfCryptException, IllegalStateException {
+
+ requireParamSet();
+ checkStateAndInitialize();
+ throwIfKeyExists();
+
+ synchronized (stateLock) {
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_import_public(in);
+ }
+ state = WolfCryptState.READY;
+ }
+ }
+
+ /**
+ * Import a raw SLH-DSA private key matching this object's parameter set.
+ *
+ * @param in raw private key bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if a key is already loaded or object
+ * released
+ */
+ public void importPrivateKey(byte[] in)
+ throws WolfCryptException, IllegalStateException {
+
+ requireParamSet();
+ checkStateAndInitialize();
+ throwIfKeyExists();
+
+ synchronized (stateLock) {
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_import_private(in);
+ }
+ state = WolfCryptState.READY;
+ }
+ }
+
+ /**
+ * Check that the loaded private and public key halves form a consistent
+ * SLH-DSA key pair.
+ *
+ * @throws WolfCryptException if the key pair is inconsistent or the native
+ * operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public void checkKey()
+ throws WolfCryptException, IllegalStateException {
+
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_check_key();
+ }
+ }
+
+ /**
+ * Export public key as DER, optionally wrapped in a SubjectPublicKeyInfo
+ * AlgorithmIdentifier (X.509 SPKI).
+ *
+ * @param withAlg if true, output is X.509 SubjectPublicKeyInfo; if false,
+ * output is the raw key DER without the AlgorithmIdentifier
+ * wrapper.
+ *
+ * @return DER-encoded public key
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] exportPublicKeyDer(boolean withAlg)
+ throws WolfCryptException, IllegalStateException {
+
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_PublicKeyToDer(withAlg);
+ }
+ }
+
+ /**
+ * Export private key as PKCS#8 PrivateKeyInfo DER (RFC 9909). For SLH-DSA
+ * the private key is a flat OCTET STRING, there is no separate
+ * private-only form.
+ *
+ * @return PKCS#8 DER-encoded private key
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if no key is loaded or object released
+ */
+ public byte[] exportPrivateKeyDer()
+ throws WolfCryptException, IllegalStateException {
+
+ checkStateAndInitialize();
+ throwIfKeyNotLoaded();
+
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_KeyToDer();
+ }
+ }
+
+ /**
+ * Import a public key from X.509 SubjectPublicKeyInfo DER. The parameter
+ * set is auto-detected from the AlgorithmIdentifier OID.
+ *
+ * @param der X.509 SPKI DER bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if a key is already loaded or object
+ * released
+ */
+ public void importPublicKeyDer(byte[] der)
+ throws WolfCryptException, IllegalStateException {
+
+ checkStateAndInitialize();
+ throwIfKeyExists();
+
+ synchronized (stateLock) {
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_PublicKeyDecode(der);
+ /* Refresh param from native, which detects it from the OID. */
+ this.param = wc_SlhDsaKey_get_param();
+ }
+ state = WolfCryptState.READY;
+ }
+ }
+
+ /**
+ * Import a private key from PKCS#8 PrivateKeyInfo DER. The parameter set
+ * is auto-detected from the AlgorithmIdentifier OID.
+ *
+ * @param der PKCS#8 DER bytes
+ *
+ * @throws WolfCryptException if native operation fails
+ * @throws IllegalStateException if a key is already loaded or object
+ * released
+ */
+ public void importPrivateKeyDer(byte[] der)
+ throws WolfCryptException, IllegalStateException {
+
+ checkStateAndInitialize();
+ throwIfKeyExists();
+
+ synchronized (stateLock) {
+ synchronized (pointerLock) {
+ wc_SlhDsaKey_PrivateKeyDecode(der);
+ this.param = wc_SlhDsaKey_get_param();
+ }
+ state = WolfCryptState.READY;
+ }
+ }
+
+ /**
+ * Get raw public key size, in bytes, for this object's parameter set.
+ *
+ * For parameter set, requires either the explicit param constructor,
+ * or a no-arg object after a DER import has detected it.
+ *
+ * @return public key size in bytes
+ *
+ * @throws WolfCryptException if native operation fails or the parameter
+ * set is not yet set
+ * @throws IllegalStateException if the object has been released
+ */
+ public int publicKeySize()
+ throws WolfCryptException, IllegalStateException {
+
+ requireParamSet();
+ checkStateAndInitialize();
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_pub_size();
+ }
+ }
+
+ /**
+ * Get raw private key size, in bytes, for this object's parameter set.
+ *
+ * For parameter set, requires either the explicit param constructor,
+ * or a no-arg object after a DER import has detected it.
+ *
+ * @return private key size in bytes
+ *
+ * @throws WolfCryptException if native operation fails or the parameter
+ * set is not yet set
+ * @throws IllegalStateException if the object has been released
+ */
+ public int privateKeySize()
+ throws WolfCryptException, IllegalStateException {
+
+ requireParamSet();
+ checkStateAndInitialize();
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_priv_size();
+ }
+ }
+
+ /**
+ * Get signature size, in bytes, for this object's parameter set.
+ *
+ * For parameter set, requires either the explicit param constructor,
+ * or a no-arg object after a DER import has detected it.
+ *
+ * @return signature size in bytes
+ *
+ * @throws WolfCryptException if native operation fails or the parameter
+ * set is not yet set
+ * @throws IllegalStateException if the object has been released
+ */
+ public int signatureSize()
+ throws WolfCryptException, IllegalStateException {
+
+ requireParamSet();
+ checkStateAndInitialize();
+ synchronized (pointerLock) {
+ return wc_SlhDsaKey_sig_size();
+ }
+ }
+
+ /**
+ * Parse and validate an X.509 SubjectPublicKeyInfo DER blob carrying an
+ * SLH-DSA public key, return the parameter set encoded in the
+ * AlgorithmIdentifier OID.
+ *
+ * The decoded native key is freed before this method returns, only the
+ * parameter set is retained.
+ *
+ * @param x509Der X.509 SubjectPublicKeyInfo DER bytes
+ *
+ * @return one of the {@code SLH_DSA_*} parameter set constants (0-11)
+ *
+ * @throws WolfCryptException if the DER is malformed or the OID is not a
+ * recognized SLH-DSA parameter set
+ */
+ public static int parseAndValidateSlhDsaPublicKeyDer(byte[] x509Der)
+ throws WolfCryptException {
+
+ return parseAndValidate(x509Der, true);
+ }
+
+ /**
+ * Parse and validate a PKCS#8 PrivateKeyInfo DER blob carrying an SLH-DSA
+ * private key, return the parameter set encoded in the
+ * AlgorithmIdentifier OID.
+ *
+ * The decoded native key is freed before this method returns, only the
+ * parameter set is retained.
+ *
+ * @param pkcs8Der PKCS#8 PrivateKeyInfo DER bytes
+ *
+ * @return one of the {@code SLH_DSA_*} parameter set constants (0-11)
+ *
+ * @throws WolfCryptException if the DER is malformed or the OID is not a
+ * recognized SLH-DSA parameter set
+ */
+ public static int parseAndValidateSlhDsaPrivateKeyDer(byte[] pkcs8Der)
+ throws WolfCryptException {
+
+ return parseAndValidate(pkcs8Der, false);
+ }
+
+ /**
+ * Parse an SLH-DSA DER blob to determine the parameter set.
+ *
+ * Imports the DER once via wc_SlhDsaKey_Public/PrivateKeyDecode, which
+ * validates the ASN.1 encoding and detects the parameter set from the
+ * AlgorithmIdentifier OID. Native wolfSSL detects the OID.
+ *
+ * @param der DER-encoded key blob (X.509 SPKI for public, PKCS#8 for
+ * private)
+ * @param isPublic true if this is a public key DER blob, false if private
+ *
+ * @return one of the {@code SLH_DSA_*} parameter set constants (0-11)
+ *
+ * @throws WolfCryptException if the DER is malformed or the OID is not a
+ * recognized SLH-DSA parameter set.
+ */
+ private static int parseAndValidate(byte[] der, boolean isPublic)
+ throws WolfCryptException {
+
+ if (der == null || der.length == 0) {
+ throw new WolfCryptException(WolfCryptError.BAD_FUNC_ARG.getCode());
+ }
+
+ try {
+ SlhDsa k = new SlhDsa();
+ try {
+ if (isPublic) {
+ k.importPublicKeyDer(der);
+ }
+ else {
+ k.importPrivateKeyDer(der);
+ }
+ return k.getParam();
+ }
+ finally {
+ k.releaseNativeStruct();
+ }
+ }
+ catch (WolfCryptException e) {
+ /* Pass through NOT_COMPILED_IN, normalize other decode
+ * failures to ASN_PARSE_E. */
+ if (e.getError() == WolfCryptError.NOT_COMPILED_IN) {
+ throw e;
+ }
+ throw new WolfCryptException(WolfCryptError.ASN_PARSE_E.getCode());
+ }
+ }
+}
diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java b/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java
index b2159bff..cae53934 100644
--- a/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java
+++ b/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java
@@ -483,8 +483,10 @@ public enum WolfCryptError {
SEQ_OVERFLOW_E (-1008),
/** ML-DSA Pairwise Consistency Test failure */
ML_DSA_PCT_E (-1016),
+ /** SLH-DSA Conditional Algorithm Self-Test (CAST) KAT failure */
+ SLH_DSA_KAT_FIPS_E (-1018),
- /** Update this to indicate last error */
+ /** Historical marker mirroring an older native WC_LAST_E value */
WC_LAST_E (-1008),
/** Last usable code */
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptLmsKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptLmsKeyFactoryTest.java
index ffd98a38..f1994127 100644
--- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptLmsKeyFactoryTest.java
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptLmsKeyFactoryTest.java
@@ -222,6 +222,44 @@ public void generatePublicAcceptsNullAlgorithmParameters()
assertArrayEquals(noParams.getEncoded(), withNull.getEncoded());
}
+ /* Both SPKI body forms are accepted: RFC 9708 with the raw key directly
+ * in the BIT STRING, and RFC 8708 with an inner OCTET STRING wrapping
+ * it. Both normalize to the canonical RFC 9708 encoding, so getEncoded()
+ * and getKeySpec() output can differ from the bytes a caller passed in. */
+ @Test
+ public void wrappedFormNormalizesToUnwrapped() throws Exception {
+ assumeEnabled();
+
+ KeyFactory kf = KeyFactory.getInstance("LMS", "wolfJCE");
+ PublicKey unwrapped = importTc1Key(kf);
+ byte[] canonical = unwrapped.getEncoded();
+
+ /* BIT STRING carries OCTET STRING { raw key } instead of the raw
+ * key directly. */
+ byte[] algId = tlv(0x30, HSS_LMS_OID);
+ byte[] bitString = tlv(0x03, concat(new byte[] { 0x00 },
+ tlv(0x04, RFC8554_TC1_PK)));
+ final byte[] spkiWrapped = tlv(0x30, concat(algId, bitString));
+
+ PublicKey wrapped =
+ kf.generatePublic(new X509EncodedKeySpec(spkiWrapped));
+
+ assertEquals(unwrapped, wrapped);
+ assertArrayEquals(canonical, wrapped.getEncoded());
+
+ /* getKeySpec() of a foreign key holding the wrapped encoding also
+ * returns the canonical unwrapped form: keys are normalized
+ * through translateKey(), not passed through byte-for-byte. */
+ PublicKey foreign = new PublicKey() {
+ public String getAlgorithm() { return "HSS/LMS"; }
+ public String getFormat() { return "X.509"; }
+ public byte[] getEncoded() { return spkiWrapped.clone(); }
+ };
+ X509EncodedKeySpec spec =
+ kf.getKeySpec(foreign, X509EncodedKeySpec.class);
+ assertArrayEquals(canonical, spec.getEncoded());
+ }
+
@Test
public void privateKeySpecRejected() throws Exception {
assumeEnabled();
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyFactoryTest.java
new file mode 100644
index 00000000..c1ebc752
--- /dev/null
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyFactoryTest.java
@@ -0,0 +1,303 @@
+/* WolfCryptSlhDsaKeyFactoryTest.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce.test;
+
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import com.wolfssl.provider.jce.WolfCryptProvider;
+import com.wolfssl.wolfcrypt.FeatureDetect;
+import com.wolfssl.wolfcrypt.test.TimedTestWatcher;
+
+/**
+ * wolfJCE tests for the SLH-DSA KeyFactory service.
+ */
+public class WolfCryptSlhDsaKeyFactoryTest {
+
+ private static final String GEN_NAME = "SLH-DSA-SHA2-128f";
+
+ private static boolean slhDsaEnabled = false;
+ private static boolean genAvailable = false;
+ private static KeyPair kp = null;
+
+ @Rule(order = Integer.MIN_VALUE)
+ public TestRule testWatcher = TimedTestWatcher.create();
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ System.out.println("JCE WolfCryptSlhDsaKeyFactoryTest Class");
+
+ Security.insertProviderAt(new WolfCryptProvider(), 1);
+ Provider p = Security.getProvider("wolfJCE");
+ assertNotNull(p);
+
+ slhDsaEnabled = FeatureDetect.SlhDsaEnabled();
+ if (!slhDsaEnabled) {
+ System.out.println("SLH-DSA test skipped: NOT_COMPILED_IN");
+ return;
+ }
+
+ try {
+ kp = KeyPairGenerator.getInstance(GEN_NAME, "wolfJCE")
+ .generateKeyPair();
+ genAvailable = true;
+ } catch (Exception e) {
+ /* SHA2-128f not compiled into this build */
+ }
+ }
+
+ private void assumeReady() {
+ Assume.assumeTrue("SLH-DSA not compiled in", slhDsaEnabled);
+ Assume.assumeTrue(GEN_NAME + " not available", genAvailable);
+ }
+
+ @Test
+ public void publicKeyX509RoundTrip() throws Exception {
+ assumeReady();
+
+ KeyFactory kf = KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+ byte[] spki = kp.getPublic().getEncoded();
+
+ PublicKey pub = kf.generatePublic(new X509EncodedKeySpec(spki));
+ assertEquals("SLH-DSA", pub.getAlgorithm());
+ assertEquals("X.509", pub.getFormat());
+ assertArrayEquals(spki, pub.getEncoded());
+
+ X509EncodedKeySpec spec =
+ kf.getKeySpec(pub, X509EncodedKeySpec.class);
+ assertArrayEquals(spki, spec.getEncoded());
+ }
+
+ @Test
+ public void privateKeyPkcs8RoundTrip() throws Exception {
+ assumeReady();
+
+ KeyFactory kf = KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+ byte[] pkcs8 = kp.getPrivate().getEncoded();
+
+ PrivateKey priv = kf.generatePrivate(new PKCS8EncodedKeySpec(pkcs8));
+ assertEquals("SLH-DSA", priv.getAlgorithm());
+ assertEquals("PKCS#8", priv.getFormat());
+ assertArrayEquals(pkcs8, priv.getEncoded());
+
+ PKCS8EncodedKeySpec spec =
+ kf.getKeySpec(priv, PKCS8EncodedKeySpec.class);
+ assertArrayEquals(pkcs8, spec.getEncoded());
+ }
+
+ @Test
+ public void generatePublicRejectsPkcs8() throws Exception {
+ assumeReady();
+
+ KeyFactory kf = KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+ try {
+ kf.generatePublic(
+ new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded()));
+ fail("expected InvalidKeySpecException");
+ } catch (InvalidKeySpecException e) {
+ /* expected */
+ }
+ }
+
+ @Test
+ public void generatePrivateRejectsX509() throws Exception {
+ assumeReady();
+
+ KeyFactory kf = KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+ try {
+ kf.generatePrivate(
+ new X509EncodedKeySpec(kp.getPublic().getEncoded()));
+ fail("expected InvalidKeySpecException");
+ } catch (InvalidKeySpecException e) {
+ /* expected */
+ }
+ }
+
+ @Test
+ public void translateKey() throws Exception {
+ assumeReady();
+
+ KeyFactory kf = KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+ PublicKey pub = (PublicKey) kf.translateKey(kp.getPublic());
+ assertEquals("SLH-DSA", pub.getAlgorithm());
+ assertArrayEquals(kp.getPublic().getEncoded(), pub.getEncoded());
+ }
+
+ @Test
+ public void getInstanceForAllNames() throws Exception {
+ Assume.assumeTrue("SLH-DSA not compiled in", slhDsaEnabled);
+
+ KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+
+ for (String n : PARAM_NAMES) {
+ KeyFactory.getInstance(n, "wolfJCE");
+ }
+
+ /* OID aliases .20 - .31 */
+ for (int i = 0; i < 12; i++) {
+ KeyFactory.getInstance(
+ "2.16.840.1.101.3.4.3." + (20 + i), "wolfJCE");
+ }
+ }
+
+ @Test
+ public void perSetFactoryRejectsMismatchedKey() throws Exception {
+ assumeReady();
+
+ /* A parameter-set-locked KeyFactory must reject keys of a
+ * different set. kp is SLH-DSA-SHA2-128f. */
+ KeyFactory kf =
+ KeyFactory.getInstance("SLH-DSA-SHAKE-128s", "wolfJCE");
+
+ try {
+ kf.generatePublic(
+ new X509EncodedKeySpec(kp.getPublic().getEncoded()));
+ fail("expected InvalidKeySpecException for set mismatch");
+ } catch (InvalidKeySpecException e) {
+ /* expected */
+ }
+
+ try {
+ kf.generatePrivate(
+ new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded()));
+ fail("expected InvalidKeySpecException for set mismatch");
+ } catch (InvalidKeySpecException e) {
+ /* expected */
+ }
+ }
+
+ @Test
+ public void nullAlgorithmIdParametersAccepted() throws Exception {
+ assumeReady();
+
+ /* RFC 9909 omits the AlgorithmIdentifier parameters for SLH-DSA,
+ * but the JDK X.509 stack can re-encode a certificate
+ * SubjectPublicKeyInfo with an explicit NULL parameters field when
+ * it hands the public key to this KeyFactory by OID. That form
+ * must decode to the same key as the canonical no-parameters form. */
+ byte[] spki = kp.getPublic().getEncoded();
+
+ /* Recover the raw public key bytes via the JNI layer. */
+ com.wolfssl.wolfcrypt.SlhDsa k = new com.wolfssl.wolfcrypt.SlhDsa();
+ byte[] rawPub;
+ try {
+ k.importPublicKeyDer(spki);
+ rawPub = k.exportPublicKey();
+ }
+ finally {
+ k.releaseNativeStruct();
+ }
+
+ /* Rebuild the SPKI with AlgorithmIdentifier parameters = NULL. */
+ byte[] algIdNull = tlv(0x30, concat(SHA2_128F_OID,
+ new byte[] { (byte) 0x05, (byte) 0x00 }));
+ byte[] bitString = tlv(0x03,
+ concat(new byte[] { 0x00 }, rawPub));
+ byte[] spkiNull = tlv(0x30, concat(algIdNull, bitString));
+
+ KeyFactory kf = KeyFactory.getInstance("SLH-DSA", "wolfJCE");
+ PublicKey pub = kf.generatePublic(new X509EncodedKeySpec(spkiNull));
+ assertEquals("SLH-DSA", pub.getAlgorithm());
+
+ /* The NULL-parameters form normalizes to the same canonical RFC 9909
+ * (absent-parameters) encoding. */
+ assertArrayEquals(spki, pub.getEncoded());
+
+ /* getKeySpec() of a foreign key holding the NULL-parameters form
+ * also returns the canonical encoding: keys are normalized through
+ * translateKey(), not passed through byte-for-byte. */
+ final byte[] foreignEncoded = spkiNull;
+ PublicKey foreign = new PublicKey() {
+ public String getAlgorithm() {
+ return "SLH-DSA";
+ }
+ public String getFormat() {
+ return "X.509";
+ }
+ public byte[] getEncoded() {
+ return foreignEncoded.clone();
+ }
+ };
+
+ X509EncodedKeySpec normalized =
+ kf.getKeySpec(foreign, X509EncodedKeySpec.class);
+
+ assertArrayEquals(spki, normalized.getEncoded());
+ }
+
+ /* OID TLV for 2.16.840.1.101.3.4.3.21 (id-slh-dsa-sha2-128f). */
+ private static final byte[] SHA2_128F_OID = {
+ (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48,
+ (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x03,
+ (byte) 0x15
+ };
+
+ /* All 12 per-set KeyFactory service names. */
+ private static final String[] PARAM_NAMES = {
+ "SLH-DSA-SHA2-128s", "SLH-DSA-SHA2-128f",
+ "SLH-DSA-SHA2-192s", "SLH-DSA-SHA2-192f",
+ "SLH-DSA-SHA2-256s", "SLH-DSA-SHA2-256f",
+ "SLH-DSA-SHAKE-128s", "SLH-DSA-SHAKE-128f",
+ "SLH-DSA-SHAKE-192s", "SLH-DSA-SHAKE-192f",
+ "SLH-DSA-SHAKE-256s", "SLH-DSA-SHAKE-256f"
+ };
+
+ /* DER TLV with a definite length (content up to 0xFFFF bytes). */
+ private static byte[] tlv(int tag, byte[] content) {
+ int n = content.length;
+ byte[] len;
+ if (n < 0x80) {
+ len = new byte[] { (byte) n };
+ } else if (n < 0x100) {
+ len = new byte[] { (byte) 0x81, (byte) n };
+ } else {
+ len = new byte[] { (byte) 0x82, (byte) (n >> 8), (byte) n };
+ }
+ byte[] out = new byte[1 + len.length + n];
+ out[0] = (byte) tag;
+ System.arraycopy(len, 0, out, 1, len.length);
+ System.arraycopy(content, 0, out, 1 + len.length, n);
+ return out;
+ }
+
+ private static byte[] concat(byte[] a, byte[] b) {
+ byte[] out = new byte[a.length + b.length];
+ System.arraycopy(a, 0, out, 0, a.length);
+ System.arraycopy(b, 0, out, a.length, b.length);
+ return out;
+ }
+}
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyPairGeneratorTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyPairGeneratorTest.java
new file mode 100644
index 00000000..17ad1fb4
--- /dev/null
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyPairGeneratorTest.java
@@ -0,0 +1,183 @@
+/* WolfCryptSlhDsaKeyPairGeneratorTest.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce.test;
+
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.Security;
+
+import com.wolfssl.provider.jce.WolfCryptProvider;
+import com.wolfssl.provider.jce.WolfPQCParameterSpec;
+import com.wolfssl.wolfcrypt.FeatureDetect;
+import com.wolfssl.wolfcrypt.SlhDsa;
+import com.wolfssl.wolfcrypt.test.TimedTestWatcher;
+
+/**
+ * wolfJCE tests for the SLH-DSA KeyPairGenerator service.
+ */
+public class WolfCryptSlhDsaKeyPairGeneratorTest {
+
+ /* All 12 FIPS 205 parameter set service names. */
+ private static final String[] PARAM_NAMES = {
+ "SLH-DSA-SHA2-128s", "SLH-DSA-SHA2-128f",
+ "SLH-DSA-SHA2-192s", "SLH-DSA-SHA2-192f",
+ "SLH-DSA-SHA2-256s", "SLH-DSA-SHA2-256f",
+ "SLH-DSA-SHAKE-128s", "SLH-DSA-SHAKE-128f",
+ "SLH-DSA-SHAKE-192s", "SLH-DSA-SHAKE-192f",
+ "SLH-DSA-SHAKE-256s", "SLH-DSA-SHAKE-256f"
+ };
+
+ /* Fast-signing sets used for the keygen round trips. */
+ private static final String[] FAST_NAMES = {
+ "SLH-DSA-SHA2-128f", "SLH-DSA-SHAKE-128f"
+ };
+
+ private static boolean slhDsaEnabled = false;
+
+ @Rule(order = Integer.MIN_VALUE)
+ public TestRule testWatcher = TimedTestWatcher.create();
+
+ @BeforeClass
+ public static void setUp() {
+ System.out.println("JCE WolfCryptSlhDsaKeyPairGeneratorTest Class");
+
+ Security.insertProviderAt(new WolfCryptProvider(), 1);
+ Provider p = Security.getProvider("wolfJCE");
+ assertNotNull(p);
+
+ /* Gate on keygen support, KeyPairGenerator.SLH-DSA services are
+ * not registered on verify-only native builds. */
+ slhDsaEnabled = FeatureDetect.SlhDsaKeyGenEnabled();
+ if (!slhDsaEnabled) {
+ System.out.println("SLH-DSA keygen test skipped: " +
+ "NOT_COMPILED_IN");
+ }
+ }
+
+ private void assumeEnabled() {
+ Assume.assumeTrue("SLH-DSA keygen not compiled in", slhDsaEnabled);
+ }
+
+ private static boolean available(String name) {
+ try {
+ KeyPairGenerator.getInstance(name, "wolfJCE").generateKeyPair();
+ return true;
+
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Test
+ public void getInstanceForAllNames() throws Exception {
+ assumeEnabled();
+
+ KeyPairGenerator.getInstance("SLH-DSA", "wolfJCE");
+ for (String n : PARAM_NAMES) {
+ KeyPairGenerator.getInstance(n, "wolfJCE");
+ }
+
+ /* OID aliases .20 - .31 */
+ for (int i = 0; i < 12; i++) {
+ KeyPairGenerator.getInstance(
+ "2.16.840.1.101.3.4.3." + (20 + i), "wolfJCE");
+ }
+ }
+
+ @Test
+ public void umbrellaDefaultsToSha2128f() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHA2-128f"));
+
+ KeyPair kp = KeyPairGenerator.getInstance("SLH-DSA", "wolfJCE")
+ .generateKeyPair();
+ assertNotNull(kp);
+ assertEquals("SLH-DSA", kp.getPublic().getAlgorithm());
+
+ /* The umbrella default must encode as the SHA2-128f OID (.21). The
+ * SubjectPublicKeyInfo carries the parameter-set OID. */
+ byte[] spki = kp.getPublic().getEncoded();
+ int param = SlhDsa.parseAndValidateSlhDsaPublicKeyDer(spki);
+ assertEquals(SlhDsa.SLH_DSA_SHA2_128F, param);
+ }
+
+ @Test
+ public void perSetKeygenRoundTrip() throws Exception {
+ assumeEnabled();
+
+ for (String n : FAST_NAMES) {
+ if (!available(n)) {
+ continue;
+ }
+ KeyPair kp = KeyPairGenerator.getInstance(n, "wolfJCE")
+ .generateKeyPair();
+ assertNotNull(kp);
+ assertEquals("SLH-DSA", kp.getPublic().getAlgorithm());
+ assertEquals("SLH-DSA", kp.getPrivate().getAlgorithm());
+ assertEquals("X.509", kp.getPublic().getFormat());
+ assertEquals("PKCS#8", kp.getPrivate().getFormat());
+ }
+ }
+
+ @Test
+ public void initializeWithIntKeySizeRejected() throws Exception {
+ assumeEnabled();
+
+ KeyPairGenerator kpg =
+ KeyPairGenerator.getInstance("SLH-DSA", "wolfJCE");
+
+ try {
+ kpg.initialize(2048);
+ fail("expected InvalidParameterException for integer key size");
+ } catch (InvalidParameterException e) {
+ /* expected, SLH-DSA has no integer key sizes */
+ }
+ }
+
+ @Test
+ public void perSetGeneratorLocksParam() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHAKE-128f"));
+
+ /* A per-set generator must not be re-pointed at a different set. */
+ KeyPairGenerator kpg =
+ KeyPairGenerator.getInstance("SLH-DSA-SHAKE-128f", "wolfJCE");
+
+ try {
+ kpg.initialize(WolfPQCParameterSpec.SLH_DSA_SHA2_256S);
+ fail("expected InvalidAlgorithmParameterException for " +
+ "set mismatch");
+ } catch (InvalidAlgorithmParameterException e) {
+ /* expected */
+ }
+ }
+}
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyTest.java
new file mode 100644
index 00000000..38f015e6
--- /dev/null
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaKeyTest.java
@@ -0,0 +1,149 @@
+/* WolfCryptSlhDsaKeyTest.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce.test;
+
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+
+import javax.security.auth.Destroyable;
+
+import com.wolfssl.provider.jce.WolfCryptProvider;
+import com.wolfssl.wolfcrypt.FeatureDetect;
+import com.wolfssl.wolfcrypt.test.TimedTestWatcher;
+
+/**
+ * wolfJCE tests for the SLH-DSA PublicKey and PrivateKey classes.
+ */
+public class WolfCryptSlhDsaKeyTest {
+
+ private static final String GEN_NAME = "SLH-DSA-SHA2-128f";
+
+ private static boolean slhDsaEnabled = false;
+ private static boolean genAvailable = false;
+ private static KeyPair kp = null;
+
+ @Rule(order = Integer.MIN_VALUE)
+ public TestRule testWatcher = TimedTestWatcher.create();
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ System.out.println("JCE WolfCryptSlhDsaKeyTest Class");
+
+ Security.insertProviderAt(new WolfCryptProvider(), 1);
+ Provider p = Security.getProvider("wolfJCE");
+ assertNotNull(p);
+
+ slhDsaEnabled = FeatureDetect.SlhDsaEnabled();
+ if (!slhDsaEnabled) {
+ System.out.println("SLH-DSA test skipped: NOT_COMPILED_IN");
+ return;
+ }
+
+ try {
+ kp = KeyPairGenerator.getInstance(GEN_NAME, "wolfJCE")
+ .generateKeyPair();
+ genAvailable = true;
+ } catch (Exception e) {
+ /* not available */
+ }
+ }
+
+ private void assumeReady() {
+ Assume.assumeTrue("SLH-DSA not compiled in", slhDsaEnabled);
+ Assume.assumeTrue(GEN_NAME + " not available", genAvailable);
+ }
+
+ @Test
+ public void publicKeyProperties() {
+ assumeReady();
+
+ PublicKey pub = kp.getPublic();
+ assertEquals("SLH-DSA", pub.getAlgorithm());
+ assertEquals("X.509", pub.getFormat());
+ assertNotNull(pub.getEncoded());
+ assertTrue(pub.getEncoded().length > 0);
+ }
+
+ @Test
+ public void privateKeyProperties() {
+ assumeReady();
+
+ PrivateKey priv = kp.getPrivate();
+ assertEquals("SLH-DSA", priv.getAlgorithm());
+ assertEquals("PKCS#8", priv.getFormat());
+ assertNotNull(priv.getEncoded());
+ assertTrue(priv.getEncoded().length > 0);
+ }
+
+ @Test
+ public void getEncodedReturnsCopy() {
+ assumeReady();
+
+ PublicKey pub = kp.getPublic();
+ byte[] a = pub.getEncoded();
+ byte[] b = pub.getEncoded();
+ assertNotSame(a, b);
+ assertArrayEquals(a, b);
+
+ /* Mutating the returned array must not affect the key. */
+ a[0] ^= (byte)0xFF;
+ assertFalse(java.util.Arrays.equals(a, pub.getEncoded()));
+ }
+
+ @Test
+ public void equalsAndHashCode() {
+ assumeReady();
+
+ PublicKey pub = kp.getPublic();
+ assertEquals(pub, pub);
+ assertEquals(pub.hashCode(), pub.hashCode());
+ assertNotEquals(pub, null);
+ assertNotEquals(pub, "not a key");
+ }
+
+ @Test
+ public void privateKeyDestroyable() throws Exception {
+ assumeReady();
+
+ /* Generate a throwaway key to destroy. */
+ KeyPair tmp = KeyPairGenerator.getInstance(GEN_NAME, "wolfJCE")
+ .generateKeyPair();
+ PrivateKey priv = tmp.getPrivate();
+ assertTrue(priv instanceof Destroyable);
+
+ Destroyable d = (Destroyable) priv;
+ assertFalse(d.isDestroyed());
+ d.destroy();
+ assertTrue(d.isDestroyed());
+ }
+}
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaSignatureTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaSignatureTest.java
new file mode 100644
index 00000000..dea9b62b
--- /dev/null
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSlhDsaSignatureTest.java
@@ -0,0 +1,519 @@
+/* WolfCryptSlhDsaSignatureTest.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce.test;
+
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+import java.util.Arrays;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.InvalidKeyException;
+
+import com.wolfssl.provider.jce.WolfCryptProvider;
+import com.wolfssl.provider.jce.WolfCryptContextParameterSpec;
+import com.wolfssl.wolfcrypt.FeatureDetect;
+import com.wolfssl.wolfcrypt.test.TimedTestWatcher;
+
+/**
+ * wolfJCE tests for the SLH-DSA Signature service.
+ */
+public class WolfCryptSlhDsaSignatureTest {
+
+ private static final String[] PARAM_NAMES = {
+ "SLH-DSA-SHA2-128s", "SLH-DSA-SHA2-128f",
+ "SLH-DSA-SHA2-192s", "SLH-DSA-SHA2-192f",
+ "SLH-DSA-SHA2-256s", "SLH-DSA-SHA2-256f",
+ "SLH-DSA-SHAKE-128s", "SLH-DSA-SHAKE-128f",
+ "SLH-DSA-SHAKE-192s", "SLH-DSA-SHAKE-192f",
+ "SLH-DSA-SHAKE-256s", "SLH-DSA-SHAKE-256f"
+ };
+
+ /* Fast-signing sets for end-to-end round trips. */
+ private static final String[] FAST_NAMES = {
+ "SLH-DSA-SHA2-128f", "SLH-DSA-SHAKE-128f"
+ };
+
+ /* HashSLH-DSA per-set service names, in the same order as their
+ * FIPS 205 OID aliases 2.16.840.1.101.3.4.3.35 - .46. */
+ private static final String[] PREHASH_NAMES = {
+ "SLH-DSA-SHA2-128s-WITH-SHA256",
+ "SLH-DSA-SHA2-128f-WITH-SHA256",
+ "SLH-DSA-SHA2-192s-WITH-SHA512",
+ "SLH-DSA-SHA2-192f-WITH-SHA512",
+ "SLH-DSA-SHA2-256s-WITH-SHA512",
+ "SLH-DSA-SHA2-256f-WITH-SHA512",
+ "SLH-DSA-SHAKE-128s-WITH-SHAKE128",
+ "SLH-DSA-SHAKE-128f-WITH-SHAKE128",
+ "SLH-DSA-SHAKE-192s-WITH-SHAKE256",
+ "SLH-DSA-SHAKE-192f-WITH-SHAKE256",
+ "SLH-DSA-SHAKE-256s-WITH-SHAKE256",
+ "SLH-DSA-SHAKE-256f-WITH-SHAKE256"
+ };
+
+ private static final byte[] MSG = "SLH-DSA JCE message".getBytes();
+
+ private static boolean slhDsaEnabled = false;
+
+ @Rule(order = Integer.MIN_VALUE)
+ public TestRule testWatcher = TimedTestWatcher.create();
+
+ @BeforeClass
+ public static void setUp() {
+ System.out.println("JCE WolfCryptSlhDsaSignatureTest Class");
+
+ Security.insertProviderAt(new WolfCryptProvider(), 1);
+ Provider p = Security.getProvider("wolfJCE");
+ assertNotNull(p);
+
+ slhDsaEnabled = FeatureDetect.SlhDsaEnabled();
+ if (!slhDsaEnabled) {
+ System.out.println("SLH-DSA test skipped: NOT_COMPILED_IN");
+ }
+ }
+
+ private void assumeEnabled() {
+ Assume.assumeTrue("SLH-DSA not compiled in", slhDsaEnabled);
+ }
+
+ private static boolean available(String name) {
+ try {
+ KeyPairGenerator.getInstance(name, "wolfJCE").generateKeyPair();
+ return true;
+
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private static KeyPair genKey(String name) throws Exception {
+ return KeyPairGenerator.getInstance(name, "wolfJCE").generateKeyPair();
+ }
+
+ @Test
+ public void getInstanceForAllNames() throws Exception {
+ assumeEnabled();
+
+ Signature.getInstance("SLH-DSA", "wolfJCE");
+
+ for (String n : PARAM_NAMES) {
+ Signature.getInstance(n, "wolfJCE");
+ }
+
+ for (int i = 0; i < 12; i++) {
+ Signature.getInstance(
+ "2.16.840.1.101.3.4.3." + (20 + i), "wolfJCE");
+ }
+ }
+
+ @Test
+ public void signVerifyUmbrella() throws Exception {
+ assumeEnabled();
+
+ for (String n : FAST_NAMES) {
+ if (!available(n)) {
+ continue;
+ }
+
+ KeyPair kp = genKey(n);
+
+ /* Umbrella "SLH-DSA" Signature works with any parameter-set key. */
+ Signature signer = Signature.getInstance("SLH-DSA", "wolfJCE");
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig = signer.sign();
+ assertNotNull(sig);
+
+ Signature verifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ verifier.initVerify(kp.getPublic());
+ verifier.update(MSG);
+ assertTrue("verify, " + n, verifier.verify(sig));
+
+ /* Tampered message must fail. */
+ Signature v2 = Signature.getInstance("SLH-DSA", "wolfJCE");
+ v2.initVerify(kp.getPublic());
+ byte[] bad = MSG.clone();
+ bad[0] ^= 0x01;
+ v2.update(bad);
+ assertFalse("tampered, " + n, v2.verify(sig));
+ }
+ }
+
+ @Test
+ public void signVerifyPerSetName() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHAKE-128f"));
+
+ KeyPair kp = genKey("SLH-DSA-SHAKE-128f");
+
+ Signature signer =
+ Signature.getInstance("SLH-DSA-SHAKE-128f", "wolfJCE");
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig = signer.sign();
+
+ Signature verifier =
+ Signature.getInstance("SLH-DSA-SHAKE-128f", "wolfJCE");
+ verifier.initVerify(kp.getPublic());
+ verifier.update(MSG);
+ assertTrue(verifier.verify(sig));
+ }
+
+ @Test
+ public void perSetSignatureRejectsMismatchedKey() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHAKE-128f"));
+ Assume.assumeTrue(available("SLH-DSA-SHA2-128f"));
+
+ KeyPair shake = genKey("SLH-DSA-SHAKE-128f");
+
+ /* A SHA2-128f-named Signature must reject a SHAKE-128f key. */
+ Signature signer =
+ Signature.getInstance("SLH-DSA-SHA2-128f", "wolfJCE");
+
+ try {
+ signer.initSign(shake.getPrivate());
+ fail("expected InvalidKeyException for parameter-set mismatch");
+ } catch (InvalidKeyException e) {
+ /* expected */
+ }
+ }
+
+ @Test
+ public void perSetPreHashSignatureRejectsMismatchedKey()
+ throws Exception {
+
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHAKE-128f"));
+ Assume.assumeTrue(available("SLH-DSA-SHA2-128f"));
+
+ KeyPair shake = genKey("SLH-DSA-SHAKE-128f");
+
+ /* A SHA2-128f pre-hash-named Signature must reject a SHAKE-128f */
+ Signature signer = Signature.getInstance(
+ "SLH-DSA-SHA2-128f-WITH-SHA256", "wolfJCE");
+
+ try {
+ signer.initSign(shake.getPrivate());
+ fail("expected InvalidKeyException for parameter-set mismatch");
+ } catch (java.security.InvalidKeyException e) {
+ /* expected */
+ }
+ }
+
+ @Test
+ public void contextStringRoundTrip() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHA2-128f"));
+
+ KeyPair kp = genKey("SLH-DSA-SHA2-128f");
+ byte[] ctx = "application-context".getBytes();
+
+ Signature signer = Signature.getInstance("SLH-DSA", "wolfJCE");
+ signer.setParameter(new WolfCryptContextParameterSpec(ctx));
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig = signer.sign();
+
+ /* Verify with the same context succeeds. */
+ Signature verifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ verifier.setParameter(new WolfCryptContextParameterSpec(ctx));
+ verifier.initVerify(kp.getPublic());
+ verifier.update(MSG);
+ assertTrue("same ctx verifies", verifier.verify(sig));
+
+ /* Verify with empty context (default) must fail. */
+ Signature v2 = Signature.getInstance("SLH-DSA", "wolfJCE");
+ v2.initVerify(kp.getPublic());
+ v2.update(MSG);
+ assertFalse("empty ctx must not verify", v2.verify(sig));
+
+ /* Verify with a different context must fail. */
+ Signature v3 = Signature.getInstance("SLH-DSA", "wolfJCE");
+ v3.setParameter(new WolfCryptContextParameterSpec("other".getBytes()));
+ v3.initVerify(kp.getPublic());
+ v3.update(MSG);
+ assertFalse("different ctx must not verify", v3.verify(sig));
+ }
+
+ @Test
+ public void contextPersistsAcrossInit() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHA2-128f"));
+
+ KeyPair kp = genKey("SLH-DSA-SHA2-128f");
+ byte[] ctx = "persistent-context".getBytes();
+
+ /* Documented behavior: a context set via setParameter() persists
+ * across init calls until replaced, matching how JCA providers
+ * treat setParameter() state (for example PSS parameters in
+ * SunRsaSign). */
+ Signature signer = Signature.getInstance("SLH-DSA", "wolfJCE");
+ signer.setParameter(new WolfCryptContextParameterSpec(ctx));
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig1 = signer.sign();
+
+ /* Re-init the same instance, the context still applies. */
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig2 = signer.sign();
+
+ Signature verifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ verifier.setParameter(new WolfCryptContextParameterSpec(ctx));
+ verifier.initVerify(kp.getPublic());
+ verifier.update(MSG);
+ assertTrue("first sig verifies with ctx", verifier.verify(sig1));
+ verifier.update(MSG);
+ assertTrue("sig after re-init verifies with ctx",
+ verifier.verify(sig2));
+
+ /* And must not verify against the empty-context default. */
+ Signature v2 = Signature.getInstance("SLH-DSA", "wolfJCE");
+ v2.initVerify(kp.getPublic());
+ v2.update(MSG);
+ assertFalse("sig after re-init must not verify with empty ctx",
+ v2.verify(sig2));
+ }
+
+ @Test
+ public void contextSpecRejectsTooLong() {
+ assumeEnabled();
+
+ byte[] tooLong = new byte[256];
+ try {
+ new WolfCryptContextParameterSpec(tooLong);
+ fail("expected IllegalArgumentException for ctx > 255");
+
+ } catch (IllegalArgumentException e) {
+ /* expected */
+ }
+ }
+
+ @Test
+ public void crossKeyVerificationFails() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHA2-128f"));
+
+ KeyPair kpA = genKey("SLH-DSA-SHA2-128f");
+ KeyPair kpB = genKey("SLH-DSA-SHA2-128f");
+
+ Signature signer = Signature.getInstance("SLH-DSA", "wolfJCE");
+ signer.initSign(kpA.getPrivate());
+ signer.update(MSG);
+ byte[] sig = signer.sign();
+
+ Signature verifier = Signature.getInstance("SLH-DSA", "wolfJCE");
+ verifier.initVerify(kpB.getPublic());
+ verifier.update(MSG);
+ assertFalse("other key must not verify", verifier.verify(sig));
+ }
+
+ @Test
+ public void hashSlhDsaGetInstance() throws Exception {
+ assumeEnabled();
+
+ Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+
+ for (String n : PREHASH_NAMES) {
+ Signature.getInstance(n, "wolfJCE");
+ }
+
+ /* OID aliases .35 - .46, same order as PREHASH_NAMES */
+ for (int i = 0; i < PREHASH_NAMES.length; i++) {
+ Signature.getInstance(
+ "2.16.840.1.101.3.4.3." + (35 + i), "wolfJCE");
+ }
+ }
+
+ @Test
+ public void hashSlhDsaSignVerify() throws Exception {
+ assumeEnabled();
+
+ for (String n : new String[] { "SLH-DSA-SHA2-128f",
+ "SLH-DSA-SHAKE-128f" }) {
+ if (!available(n)) {
+ continue;
+ }
+ KeyPair kp = genKey(n);
+
+ /* Umbrella HASH-SLH-DSA works with any SLH-DSA key. */
+ Signature signer = Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig = signer.sign();
+
+ Signature verifier =
+ Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ verifier.initVerify(kp.getPublic());
+ verifier.update(MSG);
+ assertTrue("HASH-SLH-DSA verify, " + n, verifier.verify(sig));
+
+ /* Tampered message must fail. */
+ Signature v2 = Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ v2.initVerify(kp.getPublic());
+ byte[] bad = MSG.clone();
+ bad[0] ^= 0x01;
+ v2.update(bad);
+ assertFalse("HASH-SLH-DSA tampered, " + n, v2.verify(sig));
+
+ /* Pure SLH-DSA and HashSLH-DSA use different domain separation
+ * (M' prefix 0x00 vs 0x01), so a pure signature must NOT verify
+ * as pre-hash. */
+ Signature pureSigner =
+ Signature.getInstance("SLH-DSA", "wolfJCE");
+ pureSigner.initSign(kp.getPrivate());
+ pureSigner.update(MSG);
+ byte[] pureSig = pureSigner.sign();
+ Signature hv = Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ hv.initVerify(kp.getPublic());
+ hv.update(MSG);
+ assertFalse("pure sig must not verify as pre-hash, " + n,
+ hv.verify(pureSig));
+ }
+ }
+
+ @Test
+ public void hashSlhDsaPerSetSignVerify() throws Exception {
+ assumeEnabled();
+
+ /* Per-parameter-set pre-hash services, paired with the plain name
+ * used to generate a key of that set. The fast 128f sets keep test
+ * time bounded. */
+ String[][] pairs = new String[][] {
+ { "SLH-DSA-SHA2-128f", "SLH-DSA-SHA2-128f-WITH-SHA256" },
+ { "SLH-DSA-SHAKE-128f", "SLH-DSA-SHAKE-128f-WITH-SHAKE128" }
+ };
+
+ for (String[] pair : pairs) {
+ String keyName = pair[0];
+ String sigName = pair[1];
+
+ if (!available(keyName)) {
+ continue;
+ }
+ KeyPair kp = genKey(keyName);
+
+ Signature signer = Signature.getInstance(sigName, "wolfJCE");
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig = signer.sign();
+
+ Signature verifier = Signature.getInstance(sigName, "wolfJCE");
+ verifier.initVerify(kp.getPublic());
+ verifier.update(MSG);
+ assertTrue(sigName + " verify", verifier.verify(sig));
+
+ /* Tampered message must fail. */
+ Signature v2 = Signature.getInstance(sigName, "wolfJCE");
+ v2.initVerify(kp.getPublic());
+ byte[] bad = MSG.clone();
+ bad[0] ^= 0x01;
+ v2.update(bad);
+ assertFalse(sigName + " tampered", v2.verify(sig));
+
+ /* The per-set service and the umbrella HASH-SLH-DSA use the
+ * same FIPS 205 Section 10.2.2 pre-hash for a given key, so
+ * signatures must interop between the two. */
+ Signature hv = Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ hv.initVerify(kp.getPublic());
+ hv.update(MSG);
+ assertTrue("umbrella verifies per-set sig, " + sigName,
+ hv.verify(sig));
+ }
+ }
+
+ @Test
+ public void hashSlhDsaContextRoundTrip() throws Exception {
+ assumeEnabled();
+ Assume.assumeTrue(available("SLH-DSA-SHA2-128f"));
+
+ KeyPair kp = genKey("SLH-DSA-SHA2-128f");
+ byte[] ctx = "prehash-context".getBytes();
+
+ /* Sign pre-hash with a non-empty FIPS 205 context. */
+ Signature signer = Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ signer.setParameter(new WolfCryptContextParameterSpec(ctx));
+ signer.initSign(kp.getPrivate());
+ signer.update(MSG);
+ byte[] sig = signer.sign();
+
+ /* Verify with the same context succeeds. */
+ Signature verifier = Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ verifier.setParameter(new WolfCryptContextParameterSpec(ctx));
+ verifier.initVerify(kp.getPublic());
+ verifier.update(MSG);
+ assertTrue("same ctx verifies pre-hash", verifier.verify(sig));
+
+ /* Verify with the default empty context must fail. */
+ Signature v2 = Signature.getInstance("HASH-SLH-DSA", "wolfJCE");
+ v2.initVerify(kp.getPublic());
+ v2.update(MSG);
+ assertFalse("empty ctx must not verify pre-hash", v2.verify(sig));
+ }
+
+ @Test
+ public void contextParameterSpecBehavior() {
+
+ /* null context normalizes to an empty context */
+ WolfCryptContextParameterSpec empty =
+ new WolfCryptContextParameterSpec(null);
+ assertNotNull(empty.getContext());
+ assertEquals(0, empty.getContext().length);
+
+ /* Constructor copies its input, later caller mutation of the
+ * source array must not change the spec */
+ byte[] src = "spec-context".getBytes();
+ WolfCryptContextParameterSpec spec =
+ new WolfCryptContextParameterSpec(src);
+ src[0] ^= (byte)0xFF;
+ assertFalse("ctor must copy input",
+ Arrays.equals(src, spec.getContext()));
+
+ /* getContext() returns a copy, mutating it must not change the
+ * spec */
+ byte[] out = spec.getContext();
+ out[0] ^= (byte)0xFF;
+ assertArrayEquals("getContext must return a copy",
+ "spec-context".getBytes(), spec.getContext());
+
+ /* equals/hashCode compare by content */
+ WolfCryptContextParameterSpec same =
+ new WolfCryptContextParameterSpec("spec-context".getBytes());
+ WolfCryptContextParameterSpec other =
+ new WolfCryptContextParameterSpec("different".getBytes());
+ assertEquals(spec, same);
+ assertEquals(spec.hashCode(), same.hashCode());
+ assertFalse("different contexts must not be equal",
+ spec.equals(other));
+ }
+}
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptXmssKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptXmssKeyFactoryTest.java
index 37302bf0..96275420 100644
--- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptXmssKeyFactoryTest.java
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptXmssKeyFactoryTest.java
@@ -233,7 +233,7 @@ public void wrappedOctetStringSpkiAccepted() throws Exception {
assumeEnabled();
/* Some encoders wrap the raw RFC 8391 public key in an inner OCTET
- * STRING inside the BIT STRING; WolfCryptXmssUtil accepts that form
+ * STRING inside the BIT STRING; WolfCryptSpkiUtil accepts that form
* and normalizes it to the same RFC 9802 (unwrapped) encoding. */
byte[] octet = tlv(0x04, XMSS_SHA2_10_256_PK);
byte[] algId = tlv(0x30, XMSS_OID);
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java b/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java
index 31bb5ce0..634b5fc8 100644
--- a/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java
@@ -53,6 +53,10 @@
WolfCryptMlDsaKeyPairGeneratorTest.class,
WolfCryptMlDsaKeyFactoryTest.class,
WolfCryptMlDsaInteropTest.class,
+ WolfCryptSlhDsaKeyTest.class,
+ WolfCryptSlhDsaSignatureTest.class,
+ WolfCryptSlhDsaKeyPairGeneratorTest.class,
+ WolfCryptSlhDsaKeyFactoryTest.class,
WolfCryptMlKemKeyPairGeneratorTest.class,
WolfCryptMlKemKeyFactoryTest.class,
WolfCryptMlKemKemTest.class,
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java
index 43dd3a7e..6f17a95f 100644
--- a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java
@@ -175,6 +175,17 @@ public class WolfSSLKeyStoreTest {
* examples/certs/xmss/. Loaded only if FeatureDetect.XmssEnabled(). */
private static String xmssRootCertDer = null;
+ /* SLH-DSA self-signed root certs/keys from wolfssl/certs/slhdsa/. Loaded
+ * only if FeatureDetect.SlhDsaEnabled() is true. */
+ private static String slhDsaSha2KeyPem = null;
+ private static String slhDsaSha2CertPem = null;
+ private static String slhDsaShakeKeyPem = null;
+ private static String slhDsaShakeCertPem = null;
+ private static PrivateKey keySlhDsaSha2 = null;
+ private static Certificate certSlhDsaSha2 = null;
+ private static PrivateKey keySlhDsaShake = null;
+ private static Certificate certSlhDsaShake = null;
+
/* Example .jks KeyStore file paths */
private static String clientJKS = null; /* client.jks */
@@ -507,6 +518,32 @@ private static void createTestObjects()
}
}
}
+
+ /* Load SLH-DSA self-signed test certs/keys if SLH-DSA compiled in. */
+ if (FeatureDetect.SlhDsaEnabled()) {
+ if (new File(slhDsaSha2KeyPem).exists()) {
+ try {
+ keySlhDsaSha2 =
+ pemFileToPrivateKey(slhDsaSha2KeyPem, "SLH-DSA");
+ certSlhDsaSha2 = certFileToCertificate(slhDsaSha2CertPem);
+ }
+ catch (Exception e) {
+ keySlhDsaSha2 = null;
+ certSlhDsaSha2 = null;
+ }
+ }
+ if (new File(slhDsaShakeKeyPem).exists()) {
+ try {
+ keySlhDsaShake =
+ pemFileToPrivateKey(slhDsaShakeKeyPem, "SLH-DSA");
+ certSlhDsaShake = certFileToCertificate(slhDsaShakeCertPem);
+ }
+ catch (Exception e) {
+ keySlhDsaShake = null;
+ certSlhDsaShake = null;
+ }
+ }
+ }
}
@BeforeClass
@@ -634,6 +671,16 @@ public static void testSetupAndProviderInstallation()
caMlDsa87WKS =
certPre.concat("examples/certs/ca-mldsa87.wks");
+ /* SLH-DSA test material paths */
+ slhDsaSha2KeyPem = certPre.concat(
+ "examples/certs/slhdsa/root-slhdsa-sha2-128s-priv.pem");
+ slhDsaSha2CertPem = certPre.concat(
+ "examples/certs/slhdsa/root-slhdsa-sha2-128s.pem");
+ slhDsaShakeKeyPem = certPre.concat(
+ "examples/certs/slhdsa/root-slhdsa-shake-128s-priv.pem");
+ slhDsaShakeCertPem = certPre.concat(
+ "examples/certs/slhdsa/root-slhdsa-shake-128s.pem");
+
/* Test if file exists. Skip tests gracefully if cert files not
* available (eg running on Android). */
File f = new File(serverCertDer);
@@ -3238,6 +3285,14 @@ private void assumeMlDsaAvailable() {
serverKeyMlDsa44, serverKeyMlDsa65, serverKeyMlDsa87);
}
+ private void assumeSlhDsaAvailable() {
+ Assume.assumeTrue("SLH-DSA not compiled in native wolfSSL",
+ FeatureDetect.SlhDsaEnabled());
+ Assume.assumeTrue("SLH-DSA test keys not loadable on this build "
+ + "(see examples/certs/slhdsa/)",
+ (keySlhDsaSha2 != null) || (keySlhDsaShake != null));
+ }
+
private void assumeMlDsaWksAvailable() {
assumeMlDsaAvailable();
File f = new File(serverMlDsa44WKS);
@@ -3382,6 +3437,59 @@ public void testStoreLoadByteBufferMlDsa() throws Exception {
assertTrue(reload.isCertificateEntry("trust"));
}
+ /**
+ * Store SLH-DSA private keys + self-signed certs into a WKS KeyStore,
+ * write to a byte stream, reload, and verify keys and certs round trip.
+ */
+ @Test
+ public void testStoreLoadByteBufferSlhDsa() throws Exception {
+
+ int expected = 0;
+
+ assumeSlhDsaAvailable();
+
+ KeyStore store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ if (keySlhDsaSha2 != null) {
+ store.setKeyEntry("slhSha2", keySlhDsaSha2, storePass.toCharArray(),
+ new Certificate[]{ certSlhDsaSha2 });
+ store.setCertificateEntry("trustSha2", certSlhDsaSha2);
+ expected += 2;
+ }
+ if (keySlhDsaShake != null) {
+ store.setKeyEntry("slhShake", keySlhDsaShake,
+ storePass.toCharArray(), new Certificate[]{ certSlhDsaShake });
+ expected += 1;
+ }
+ assertEquals(expected, store.size());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ store.store(baos, storePass.toCharArray());
+ byte[] wksBytes = baos.toByteArray();
+ assertTrue(wksBytes.length > 0);
+
+ KeyStore reload = KeyStore.getInstance(storeType, storeProvider);
+ reload.load(new ByteArrayInputStream(wksBytes),
+ storePass.toCharArray());
+ assertEquals(expected, reload.size());
+
+ if (keySlhDsaSha2 != null) {
+ assertEquals(keySlhDsaSha2,
+ reload.getKey("slhSha2", storePass.toCharArray()));
+ assertEquals(certSlhDsaSha2, reload.getCertificate("slhSha2"));
+ assertEquals(certSlhDsaSha2,
+ reload.getCertificate("trustSha2"));
+ assertTrue(reload.isCertificateEntry("trustSha2"));
+ }
+ if (keySlhDsaShake != null) {
+ assertEquals(keySlhDsaShake,
+ reload.getKey("slhShake", storePass.toCharArray()));
+ assertEquals(certSlhDsaShake,
+ reload.getCertificate("slhShake"));
+ }
+ }
+
/**
* Load each prebuilt ML-DSA WKS keystore file from disk (built by
* examples/certs/BuildMlDsaKeystores.java), retrieve the key and cert,
diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java
index 86aebf6c..6c7da8ac 100644
--- a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java
+++ b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java
@@ -119,15 +119,17 @@ public void testGetMinRsaSize() {
* Helper to make an RSA key, retrying on transient PRIME_GEN_E up to
* {@link #MAKE_KEY_MAX_ATTEMPTS} times. Any other error, or a PRIME_GEN_E
* on the final attempt, is propagated. Each attempt uses a fresh key.
+ *
+ * @return new Rsa object holding the generated key, caller must
+ * release with releaseNativeStruct()
*/
- private void makeKeyWithRetry(int size, long e, Rng rng) {
+ private Rsa makeKeyWithRetry(int size, long e, Rng rng) {
for (int i = 0; i < MAKE_KEY_MAX_ATTEMPTS; i++) {
Rsa key = new Rsa();
try {
key.makeKey(size, e, rng);
- key.releaseNativeStruct();
- return;
+ return key;
} catch (WolfCryptException ex) {
key.releaseNativeStruct();
if (ex.getError() == WolfCryptError.PRIME_GEN_E &&
@@ -138,6 +140,9 @@ private void makeKeyWithRetry(int size, long e, Rng rng) {
throw ex;
}
}
+
+ /* Not reachable, the loop always returns or throws */
+ throw new IllegalStateException("RSA makeKey retry loop exited");
}
@Test
@@ -146,20 +151,19 @@ public void testMakeKey() {
/* FIPS after 2425 doesn't allow 1024-bit RSA key gen */
if ((Fips.enabled && Fips.fipsVersion < 5) ||
(!Fips.enabled && Rsa.RSA_MIN_SIZE <= 1024)) {
- makeKeyWithRetry(1024, 65537, rng);
+ makeKeyWithRetry(1024, 65537, rng).releaseNativeStruct();
}
- makeKeyWithRetry(2048, 65537, rng);
- makeKeyWithRetry(3072, 65537, rng);
- makeKeyWithRetry(4096, 65537, rng);
+ makeKeyWithRetry(2048, 65537, rng).releaseNativeStruct();
+ makeKeyWithRetry(3072, 65537, rng).releaseNativeStruct();
+ makeKeyWithRetry(4096, 65537, rng).releaseNativeStruct();
}
@Test
public void testDerExportImportSignVerify() {
/* generate new 2048-bit key */
- Rsa key = new Rsa();
- key.makeKey(2048, 65537, rng);
+ Rsa key = makeKeyWithRetry(2048, 65537, rng);
/* export key to DER */
byte[] rsaDer = key.exportPrivateDer();
@@ -306,8 +310,7 @@ public void rsaPrivateToPkcs8() {
key.releaseNativeStruct();
/* Test that generated key encodes without error */
- key = new Rsa();
- key.makeKey(1024, 65537, rng);
+ key = makeKeyWithRetry(1024, 65537, rng);
pkcs8 = key.privateKeyEncodePKCS8();
assertTrue(pkcs8 != null);
assertTrue(pkcs8.length != 0);
@@ -1369,11 +1372,10 @@ public void testRsaPssComprehensiveMgfTesting() {
return;
}
- Rsa key = new Rsa();
Rng rng = new Rng();
rng.init();
- key.makeKey(2048, 65537, rng);
+ Rsa key = makeKeyWithRetry(2048, 65537, rng);
String message = "Comprehensive MGF testing";
@@ -1453,11 +1455,10 @@ public void testRsaPssErrorConditions() {
return;
}
- Rsa key = new Rsa();
Rng rng = new Rng();
rng.init();
- key.makeKey(2048, 65537, rng);
+ Rsa key = makeKeyWithRetry(2048, 65537, rng);
String message = "Error condition testing";
byte[] digest = sha256(message.getBytes());
@@ -1506,11 +1507,10 @@ public void testRsaPssIndividualMgfTypes() {
return;
}
- Rsa key = new Rsa();
Rng rng = new Rng();
rng.init();
- key.makeKey(2048, 65537, rng);
+ Rsa key = makeKeyWithRetry(2048, 65537, rng);
String message = "Individual MGF testing";
diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/SlhDsaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/SlhDsaTest.java
new file mode 100644
index 00000000..67830330
--- /dev/null
+++ b/src/test/java/com/wolfssl/wolfcrypt/test/SlhDsaTest.java
@@ -0,0 +1,1761 @@
+/* SlhDsaTest.java
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.wolfcrypt.test;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+
+import com.wolfssl.wolfcrypt.FeatureDetect;
+import com.wolfssl.wolfcrypt.SlhDsa;
+import com.wolfssl.wolfcrypt.Rng;
+import com.wolfssl.wolfcrypt.Sha256;
+import com.wolfssl.wolfcrypt.Sha512;
+import com.wolfssl.wolfcrypt.WolfCrypt;
+import com.wolfssl.wolfcrypt.WolfCryptError;
+import com.wolfssl.wolfcrypt.WolfCryptException;
+
+public class SlhDsaTest {
+
+ /* All 12 FIPS 205 parameter sets, matching native enum SlhDsaParam. */
+ private static final int[] ALL_PARAMS = {
+ SlhDsa.SLH_DSA_SHAKE_128S, SlhDsa.SLH_DSA_SHAKE_128F,
+ SlhDsa.SLH_DSA_SHAKE_192S, SlhDsa.SLH_DSA_SHAKE_192F,
+ SlhDsa.SLH_DSA_SHAKE_256S, SlhDsa.SLH_DSA_SHAKE_256F,
+ SlhDsa.SLH_DSA_SHA2_128S, SlhDsa.SLH_DSA_SHA2_128F,
+ SlhDsa.SLH_DSA_SHA2_192S, SlhDsa.SLH_DSA_SHA2_192F,
+ SlhDsa.SLH_DSA_SHA2_256S, SlhDsa.SLH_DSA_SHA2_256F
+ };
+
+ /* FIPS 205 Table 2 sizes, indexed by parameter set 0-11. */
+ private static final int[] EXPECTED_PUB_SIZE = {
+ 32, 32, 48, 48, 64, 64, 32, 32, 48, 48, 64, 64
+ };
+ private static final int[] EXPECTED_PRIV_SIZE = {
+ 64, 64, 96, 96, 128, 128, 64, 64, 96, 96, 128, 128
+ };
+ private static final int[] EXPECTED_SIG_SIZE = {
+ 7856, 17088, 16224, 35664, 29792, 49856,
+ 7856, 17088, 16224, 35664, 29792, 49856
+ };
+
+ /* Cheap-to-run parameter sets used for the sign/verify round trips, kept
+ * to the 128-bit category to bound test time. The larger and 's' sets are
+ * exercised by the size checks and the NIST KAT vectors. */
+ private static final int[] CORE_PARAMS = {
+ SlhDsa.SLH_DSA_SHAKE_128S, SlhDsa.SLH_DSA_SHAKE_128F,
+ SlhDsa.SLH_DSA_SHA2_128S, SlhDsa.SLH_DSA_SHA2_128F
+ };
+
+ private static Rng rng = new Rng();
+ private static final Object rngLock = new Object();
+ private static boolean slhDsaEnabled = false;
+
+ /* Parameter sets actually compiled into this native wolfSSL build. */
+ private static final ArrayList availableParams =
+ new ArrayList();
+
+ @Rule(order = Integer.MIN_VALUE)
+ public TestRule testWatcher = TimedTestWatcher.create();
+
+ @BeforeClass
+ public static void setUpRng() {
+ synchronized (rngLock) {
+ rng.init();
+ }
+ }
+
+ @BeforeClass
+ public static void checkAvailability() {
+ slhDsaEnabled = FeatureDetect.SlhDsaEnabled();
+ if (!slhDsaEnabled) {
+ System.out.println("SLH-DSA test skipped: NOT_COMPILED_IN");
+ return;
+ }
+ System.out.println("JNI SlhDsa Class");
+
+ /* Probe each parameter set. publicKeySize() forces a native Init,
+ * which fails for parameter sets not compiled into this build. */
+ for (int p : ALL_PARAMS) {
+ try {
+ SlhDsa k = new SlhDsa(p);
+ try {
+ k.publicKeySize();
+ availableParams.add(p);
+ }
+ finally {
+ k.releaseNativeStruct();
+ }
+ }
+ catch (WolfCryptException e) {
+ /* parameter set not compiled in */
+ }
+ }
+ }
+
+ private void assumeEnabled() {
+ Assume.assumeTrue("SLH-DSA not compiled in", slhDsaEnabled);
+ }
+
+ private static boolean isAvailable(int param) {
+ return availableParams.contains(param);
+ }
+
+ /**
+ * Skip test if WolfCryptException is NOT_COMPILED_IN, otherwise rethrow.
+ * Key generation and signing throw NOT_COMPILED_IN on a native
+ * WOLFSSL_SLHDSA_VERIFY_ONLY build.
+ */
+ private static void skipIfNotCompiledIn(WolfCryptException e) {
+
+ if (e.getError() == WolfCryptError.NOT_COMPILED_IN) {
+ Assume.assumeNoException(e);
+ }
+ throw e;
+ }
+
+ private SlhDsa makeKey(int param) {
+ SlhDsa key = new SlhDsa(param);
+ try {
+ synchronized (rngLock) {
+ key.makeKey(rng);
+ }
+ }
+ catch (WolfCryptException e) {
+ key.releaseNativeStruct();
+ skipIfNotCompiledIn(e);
+ }
+ return key;
+ }
+
+ @Test
+ public void constructorRejectsBadParam() {
+ assumeEnabled();
+
+ try {
+ new SlhDsa(-1);
+ fail("expected WolfCryptException for invalid parameter set");
+ } catch (WolfCryptException e) {
+ assertEquals(WolfCryptError.BAD_FUNC_ARG, e.getError());
+ }
+
+ try {
+ new SlhDsa(12);
+ fail("expected WolfCryptException for invalid parameter set");
+ } catch (WolfCryptException e) {
+ assertEquals(WolfCryptError.BAD_FUNC_ARG, e.getError());
+ }
+ }
+
+ @Test
+ public void getParamReturnsConstructorParam() {
+ assumeEnabled();
+
+ for (int p : availableParams) {
+ SlhDsa key = new SlhDsa(p);
+ try {
+ assertEquals(p, key.getParam());
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void paramToNMatchesExpected() {
+ assumeEnabled();
+
+ assertEquals(16, SlhDsa.paramToN(SlhDsa.SLH_DSA_SHAKE_128S));
+ assertEquals(16, SlhDsa.paramToN(SlhDsa.SLH_DSA_SHAKE_128F));
+ assertEquals(24, SlhDsa.paramToN(SlhDsa.SLH_DSA_SHAKE_192S));
+ assertEquals(32, SlhDsa.paramToN(SlhDsa.SLH_DSA_SHAKE_256F));
+ assertEquals(16, SlhDsa.paramToN(SlhDsa.SLH_DSA_SHA2_128S));
+ assertEquals(24, SlhDsa.paramToN(SlhDsa.SLH_DSA_SHA2_192F));
+ assertEquals(32, SlhDsa.paramToN(SlhDsa.SLH_DSA_SHA2_256S));
+ }
+
+ @Test
+ public void sizesMatchExpected() {
+ assumeEnabled();
+
+ for (int p : availableParams) {
+ SlhDsa key = new SlhDsa(p);
+ try {
+ assertEquals("pub size, param " + p,
+ EXPECTED_PUB_SIZE[p], key.publicKeySize());
+ try {
+ assertEquals("priv size, param " + p,
+ EXPECTED_PRIV_SIZE[p], key.privateKeySize());
+ }
+ catch (WolfCryptException e) {
+ /* No private key support on a verify-only native build,
+ * still check public and signature sizes */
+ if (e.getError() != WolfCryptError.NOT_COMPILED_IN) {
+ throw e;
+ }
+ }
+ assertEquals("sig size, param " + p,
+ EXPECTED_SIG_SIZE[p], key.signatureSize());
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void deferredObjectRejectsSizeQueries() {
+ assumeEnabled();
+
+ /* A no-arg (deferred parameter set) object has no meaningful sizes
+ * before a DER import detects the parameter set. */
+ SlhDsa key = new SlhDsa();
+ try {
+ try {
+ key.publicKeySize();
+ fail("expected BAD_FUNC_ARG for deferred publicKeySize()");
+ } catch (WolfCryptException e) {
+ assertEquals(WolfCryptError.BAD_FUNC_ARG, e.getError());
+ }
+
+ try {
+ key.privateKeySize();
+ fail("expected BAD_FUNC_ARG for deferred privateKeySize()");
+ } catch (WolfCryptException e) {
+ assertEquals(WolfCryptError.BAD_FUNC_ARG, e.getError());
+ }
+
+ try {
+ key.signatureSize();
+ fail("expected BAD_FUNC_ARG for deferred signatureSize()");
+ } catch (WolfCryptException e) {
+ assertEquals(WolfCryptError.BAD_FUNC_ARG, e.getError());
+ }
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+
+ @Test
+ public void signVerifyRoundTrip() {
+ assumeEnabled();
+
+ byte[] msg = "SLH-DSA round trip message".getBytes();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa key = makeKey(p);
+ try {
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = key.sign(msg, rng);
+ }
+ assertEquals(EXPECTED_SIG_SIZE[p], sig.length);
+ assertTrue("verify, param " + p, key.verify(sig, msg));
+
+ /* Tampered message must fail. */
+ byte[] bad = msg.clone();
+ bad[0] ^= 0x01;
+ assertFalse("tampered msg, param " + p, key.verify(sig, bad));
+
+ /* Tampered signature must fail. */
+ byte[] badSig = sig.clone();
+ badSig[0] ^= 0x01;
+ assertFalse("tampered sig, param " + p,
+ key.verify(badSig, msg));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void verifyWrongLengthSignatureReturnsFalse() {
+ assumeEnabled();
+
+ byte[] msg = "wrong length sig".getBytes();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa key = makeKey(p);
+ try {
+ /* A too-short and a too-long signature are verification
+ * failures, not errors: verify() must return false rather
+ * than throw (native returns BAD_LENGTH_E, not SIG_VERIFY_E,
+ * for a sigSz != params->sigLen mismatch). */
+ byte[] tooShort = new byte[100];
+ assertFalse("short sig, param " + p, key.verify(tooShort, msg));
+
+ byte[] tooLong = new byte[EXPECTED_SIG_SIZE[p] + 16];
+ assertFalse("long sig, param " + p, key.verify(tooLong, msg));
+
+ byte[] empty = new byte[0];
+ assertFalse("empty sig, param " + p, key.verify(empty, msg));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void signVerifyWithContext() {
+ assumeEnabled();
+
+ byte[] msg = "context test".getBytes();
+ byte[] ctx = "my-context".getBytes();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa key = makeKey(p);
+ try {
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = key.sign(msg, ctx, rng);
+ }
+ assertTrue("verify ctx, param " + p, key.verify(sig, msg, ctx));
+
+ /* Empty context must not verify a non-empty-context sig. */
+ assertFalse("empty ctx mismatch, param " + p,
+ key.verify(sig, msg));
+
+ /* Different context must not verify. */
+ byte[] otherCtx = "other-context".getBytes();
+ assertFalse("ctx mismatch, param " + p,
+ key.verify(sig, msg, otherCtx));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void contextTooLongRejected() {
+ assumeEnabled();
+ Assume.assumeTrue(isAvailable(SlhDsa.SLH_DSA_SHAKE_128F));
+
+ byte[] msg = "msg".getBytes();
+ byte[] longCtx = new byte[256];
+
+ SlhDsa key = makeKey(SlhDsa.SLH_DSA_SHAKE_128F);
+ try {
+ synchronized (rngLock) {
+ key.sign(msg, longCtx, rng);
+ }
+ fail("expected IllegalArgumentException for ctx > 255");
+ } catch (IllegalArgumentException e) {
+ /* expected */
+ } finally {
+ key.releaseNativeStruct();
+ }
+ }
+
+ @Test
+ public void deterministicSignIsRepeatable() {
+ assumeEnabled();
+
+ byte[] msg = "deterministic".getBytes();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa key = makeKey(p);
+ try {
+ byte[] sig1 = key.signDeterministic(msg, null);
+ byte[] sig2 = key.signDeterministic(msg, null);
+ assertArrayEquals("deterministic, param " + p, sig1, sig2);
+ assertTrue("det verify, param " + p, key.verify(sig1, msg));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void makeKeyWithSeedsDeterministic() {
+ assumeEnabled();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ int n = SlhDsa.paramToN(p);
+ byte[] skSeed = new byte[n];
+ byte[] skPrf = new byte[n];
+ byte[] pkSeed = new byte[n];
+ for (int i = 0; i < n; i++) {
+ skSeed[i] = (byte)(i + 1);
+ skPrf[i] = (byte)(i + 100);
+ pkSeed[i] = (byte)(i + 200);
+ }
+
+ SlhDsa k1 = new SlhDsa(p);
+ SlhDsa k2 = new SlhDsa(p);
+ try {
+ k1.makeKeyWithSeeds(skSeed, skPrf, pkSeed);
+ k2.makeKeyWithSeeds(skSeed, skPrf, pkSeed);
+ assertArrayEquals("pub determinism, param " + p,
+ k1.exportPublicKey(), k2.exportPublicKey());
+ assertArrayEquals("priv determinism, param " + p,
+ k1.exportPrivateKey(), k2.exportPrivateKey());
+ }
+ catch (WolfCryptException e) {
+ skipIfNotCompiledIn(e);
+ }
+ finally {
+ k1.releaseNativeStruct();
+ k2.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void rawExportImportRoundTrip() {
+ assumeEnabled();
+
+ byte[] msg = "raw import".getBytes();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa signer = makeKey(p);
+ try {
+ byte[] pub = signer.exportPublicKey();
+ byte[] priv = signer.exportPrivateKey();
+ assertEquals(EXPECTED_PUB_SIZE[p], pub.length);
+ assertEquals(EXPECTED_PRIV_SIZE[p], priv.length);
+
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = signer.sign(msg, rng);
+ }
+
+ /* Import raw public into a fresh object and verify. */
+ SlhDsa verifier = new SlhDsa(p);
+ try {
+ verifier.importPublicKey(pub);
+ assertTrue("raw pub verify, param " + p,
+ verifier.verify(sig, msg));
+ }
+ finally {
+ verifier.releaseNativeStruct();
+ }
+
+ /* Import raw private into a fresh object and sign. */
+ SlhDsa signer2 = new SlhDsa(p);
+ try {
+ signer2.importPrivateKey(priv);
+ byte[] sig2;
+ synchronized (rngLock) {
+ sig2 = signer2.sign(msg, rng);
+ }
+ assertTrue("raw priv sign+verify, param " + p,
+ signer.verify(sig2, msg));
+ }
+ finally {
+ signer2.releaseNativeStruct();
+ }
+ }
+ finally {
+ signer.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void checkKeyAcceptsValidPairRejectsMismatched() {
+ assumeEnabled();
+
+ for (int p : CORE_PARAMS) {
+
+ if (!isAvailable(p)) {
+ continue;
+ }
+
+ SlhDsa keyA = makeKey(p);
+ SlhDsa keyB = makeKey(p);
+ SlhDsa mismatched = null;
+
+ try {
+ keyA.checkKey();
+
+ /* A raw private key is SK.seed || SK.prf || PK.seed ||
+ * PK.root (4n bytes). Splice key B's public half (last 2n
+ * bytes) onto key A's private half to build a mismatched
+ * pair. */
+ byte[] spliced = keyA.exportPrivateKey();
+ byte[] pubB = keyB.exportPublicKey();
+ System.arraycopy(pubB, 0, spliced,
+ spliced.length - pubB.length, pubB.length);
+
+ mismatched = new SlhDsa(p);
+ try {
+ mismatched.importPrivateKey(spliced);
+ mismatched.checkKey();
+ fail("expected WolfCryptException for mismatched key " +
+ "pair, param " + p);
+ } catch (WolfCryptException e) {
+ /* expected, mismatch detected at import or check */
+ }
+ }
+ finally {
+ keyA.releaseNativeStruct();
+ keyB.releaseNativeStruct();
+ if (mismatched != null) {
+ mismatched.releaseNativeStruct();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void publicKeyDerRawFormMatchesRawExport() {
+ assumeEnabled();
+
+ for (int p : CORE_PARAMS) {
+
+ if (!isAvailable(p)) {
+ continue;
+ }
+
+ SlhDsa key = makeKey(p);
+
+ try {
+ /* withAlg=false is the raw public key without the
+ * SubjectPublicKeyInfo wrapper, byte-identical to
+ * exportPublicKey(). */
+ byte[] raw = key.exportPublicKeyDer(false);
+ assertEquals("raw form size, param " + p,
+ EXPECTED_PUB_SIZE[p], raw.length);
+ assertArrayEquals("raw form, param " + p,
+ key.exportPublicKey(), raw);
+
+ /* The SPKI form embeds the same raw key at its tail. */
+ byte[] spki = key.exportPublicKeyDer(true);
+ assertTrue(spki.length > raw.length);
+ assertArrayEquals("SPKI tail, param " + p, raw,
+ Arrays.copyOfRange(spki, spki.length - raw.length,
+ spki.length));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void publicKeyDerRoundTrip() {
+ assumeEnabled();
+
+ byte[] msg = "spki".getBytes();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa signer = makeKey(p);
+ try {
+ byte[] spki = signer.exportPublicKeyDer(true);
+ assertNotNull(spki);
+ assertTrue(spki.length > EXPECTED_PUB_SIZE[p]);
+
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = signer.sign(msg, rng);
+ }
+
+ /* Decode SPKI with the no-arg auto-detect constructor. */
+ SlhDsa verifier = new SlhDsa();
+ try {
+ verifier.importPublicKeyDer(spki);
+ assertEquals("auto-detect param, set " + p,
+ p, verifier.getParam());
+ assertTrue("SPKI verify, param " + p,
+ verifier.verify(sig, msg));
+ }
+ finally {
+ verifier.releaseNativeStruct();
+ }
+ }
+ finally {
+ signer.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void privateKeyDerRoundTrip() {
+ assumeEnabled();
+
+ byte[] msg = "pkcs8".getBytes();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa signer = makeKey(p);
+ try {
+ byte[] pkcs8 = signer.exportPrivateKeyDer();
+ assertNotNull(pkcs8);
+
+ SlhDsa signer2 = new SlhDsa();
+ try {
+ signer2.importPrivateKeyDer(pkcs8);
+ assertEquals("auto-detect param, set " + p,
+ p, signer2.getParam());
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = signer2.sign(msg, rng);
+ }
+ assertTrue("PKCS8 sign+verify, param " + p,
+ signer.verify(sig, msg));
+ }
+ finally {
+ signer2.releaseNativeStruct();
+ }
+ }
+ finally {
+ signer.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void parseAndValidateDer() {
+ assumeEnabled();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa signer = makeKey(p);
+ try {
+ byte[] spki = signer.exportPublicKeyDer(true);
+ byte[] pkcs8 = signer.exportPrivateKeyDer();
+
+ assertEquals("public parse, param " + p, p,
+ SlhDsa.parseAndValidateSlhDsaPublicKeyDer(spki));
+ assertEquals("private parse, param " + p, p,
+ SlhDsa.parseAndValidateSlhDsaPrivateKeyDer(pkcs8));
+ }
+ finally {
+ signer.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void preHashSignVerifySha2() {
+ assumeEnabled();
+
+ byte[] msg = "pre-hash message".getBytes();
+
+ /* SHA2-128 uses SHA-256, SHA2-192/256 use SHA-512. Exercise the SHA2
+ * sets only here, the SHAKE sets need the SHAKE hash-type constants
+ * added alongside the JCE pre-hash support. */
+ runPreHash(SlhDsa.SLH_DSA_SHA2_128S, WolfCrypt.WC_HASH_TYPE_SHA256,
+ sha256(msg), msg);
+ runPreHash(SlhDsa.SLH_DSA_SHA2_128F, WolfCrypt.WC_HASH_TYPE_SHA256,
+ sha256(msg), msg);
+ }
+
+ @Test
+ public void signPreHashRoundTripAllAvailable() {
+ assumeEnabled();
+
+ byte[] msg = "HashSLH-DSA message".getBytes();
+
+ /* Message-based HashSLH-DSA: native picks the FIPS 205 pre-hash
+ * function from the parameter set (SHA-256/512 for SHA2 sets,
+ * SHAKE128/256 for SHAKE sets). */
+ for (int p : availableParams) {
+ SlhDsa key = makeKey(p);
+ try {
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = key.signPreHash(msg, rng);
+ }
+ assertTrue("pre-hash verify, param " + p,
+ key.verifyPreHash(sig, msg));
+
+ byte[] bad = msg.clone();
+ bad[0] ^= 0x01;
+ assertFalse("pre-hash tampered, param " + p,
+ key.verifyPreHash(sig, bad));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void preHashContextRoundTrip() {
+ assumeEnabled();
+
+ byte[] msg = "ph ctx".getBytes();
+ byte[] ctx = "ph-context".getBytes();
+
+ /* Bound to the fast 128f sets across both families. */
+ for (int p : new int[] { SlhDsa.SLH_DSA_SHA2_128F,
+ SlhDsa.SLH_DSA_SHAKE_128F }) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa key = makeKey(p);
+ try {
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = key.signPreHash(msg, ctx, rng);
+ }
+ assertTrue("same ctx, param " + p,
+ key.verifyPreHash(sig, msg, ctx));
+ assertFalse("empty ctx mismatch, param " + p,
+ key.verifyPreHash(sig, msg));
+ assertFalse("diff ctx mismatch, param " + p,
+ key.verifyPreHash(sig, msg, "other".getBytes()));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void preHashPairingMatchesSha2() {
+ assumeEnabled();
+
+ byte[] msg = "pre-hash pairing check".getBytes();
+
+ /* Independently validate the FIPS 205 Section 10.2.2 pre-hash pairing
+ * for the SHA2 sets. signPreHash() chooses the hash in native C. Here
+ * we reconstruct the expected digest and hashType in Java and confirm
+ * the signature verifies through the explicit-digest verifyHash()
+ * path, then that the other SHA-2 hash does NOT verify. Sign and verify
+ * do not share the digest helper, so a wrong-but-consistent pairing is
+ * caught (128-bit sets use SHA-256, 192/256-bit sets use SHA-512). The
+ * SHAKE sets cannot be cross-checked here (no Java SHAKE digest). */
+ checkSha2Pairing(SlhDsa.SLH_DSA_SHA2_128F, msg,
+ sha256(msg), WolfCrypt.WC_HASH_TYPE_SHA256,
+ sha512(msg), WolfCrypt.WC_HASH_TYPE_SHA512);
+ checkSha2Pairing(SlhDsa.SLH_DSA_SHA2_192F, msg,
+ sha512(msg), WolfCrypt.WC_HASH_TYPE_SHA512,
+ sha256(msg), WolfCrypt.WC_HASH_TYPE_SHA256);
+ }
+
+ @Test
+ public void preHashRejectsUnsupportedHashAlg() {
+ assumeEnabled();
+
+ for (int p : CORE_PARAMS) {
+ if (!isAvailable(p)) {
+ continue;
+ }
+ SlhDsa key = makeKey(p);
+ try {
+ byte[] hash = new byte[32];
+
+ /* MD5 is not a permitted FIPS 205 pre-hash function. */
+ try {
+ synchronized (rngLock) {
+ key.signHash(hash, WolfCrypt.WC_HASH_TYPE_MD5, rng);
+ }
+ fail("expected WolfCryptException for MD5 pre-hash");
+ } catch (WolfCryptException e) {
+ /* expected */
+ }
+
+ /* Out-of-range hash type value. */
+ try {
+ synchronized (rngLock) {
+ key.signHash(hash, 0x7fffffff, rng);
+ }
+ fail("expected WolfCryptException for bad hashAlg");
+ } catch (WolfCryptException e) {
+ /* expected */
+ }
+
+ /* verifyHash with an unsupported hash type is an error
+ * (throws), not a false return. Signature is sized
+ * correctly so the failure is the hash type itself. */
+ byte[] sig = new byte[key.signatureSize()];
+ try {
+ key.verifyHash(sig, hash, WolfCrypt.WC_HASH_TYPE_MD5);
+ fail("expected WolfCryptException for MD5 pre-hash " +
+ "verify");
+ } catch (WolfCryptException e) {
+ /* expected */
+ }
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+
+ /* Error mapping is parameter-set independent, one set is
+ * enough. */
+ break;
+ }
+ }
+
+ private void checkSha2Pairing(int param, byte[] msg,
+ byte[] expectDigest, int expectHashAlg,
+ byte[] wrongDigest, int wrongHashAlg) {
+
+ if (!isAvailable(param)) {
+ return;
+ }
+ SlhDsa key = makeKey(param);
+ try {
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = key.signPreHash(msg, rng);
+ }
+ assertTrue("expected pairing, param " + param,
+ key.verifyHash(sig, expectDigest, expectHashAlg));
+ assertFalse("wrong pairing, param " + param,
+ key.verifyHash(sig, wrongDigest, wrongHashAlg));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+
+ private void runPreHash(int param, int hashAlg, byte[] hash, byte[] msg) {
+ if (!isAvailable(param)) {
+ return;
+ }
+ SlhDsa key = makeKey(param);
+ try {
+ byte[] sig;
+ synchronized (rngLock) {
+ sig = key.signHash(hash, hashAlg, rng);
+ }
+ assertTrue("pre-hash verify, param " + param,
+ key.verifyHash(sig, hash, hashAlg));
+
+ byte[] badHash = hash.clone();
+ badHash[0] ^= 0x01;
+ assertFalse("pre-hash tampered, param " + param,
+ key.verifyHash(sig, badHash, hashAlg));
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+
+ private static byte[] sha256(byte[] in) {
+ Sha256 sha = new Sha256();
+ sha.update(in);
+ return sha.digest();
+ }
+
+ private static byte[] sha512(byte[] in) {
+ Sha512 sha = new Sha512();
+ sha.update(in);
+ return sha.digest();
+ }
+
+ /* NIST CAVP/ACVP SLH-DSA keyGen known-answer vectors, ported
+ * from wolfSSL wolfcrypt/test/test.c slhdsa_keygen_kat(). Each
+ * set is { param, name, sk_seed, sk_prf, pk_seed, expected_sk,
+ * expected_pk } as hex. (SHAKE-128s has no published vector.) */
+ private static final class KatVector {
+ final int param;
+ final String name;
+ final byte[] skSeed;
+ final byte[] skPrf;
+ final byte[] pkSeed;
+ final byte[] expectedSk;
+ final byte[] expectedPk;
+ KatVector(int param, String name, String skSeed,
+ String skPrf, String pkSeed, String expectedSk,
+ String expectedPk) {
+ this.param = param;
+ this.name = name;
+ this.skSeed = hexToBytes(skSeed);
+ this.skPrf = hexToBytes(skPrf);
+ this.pkSeed = hexToBytes(pkSeed);
+ this.expectedSk = hexToBytes(expectedSk);
+ this.expectedPk = hexToBytes(expectedPk);
+ }
+ }
+
+ private static final KatVector[] KAT_VECTORS = {
+ new KatVector(SlhDsa.SLH_DSA_SHA2_128S,
+ "SLH-DSA-SHA2-128s",
+ /* sk_seed */
+ "173d04c938c1c36bf289c3c022d04b14",
+ /* sk_prf */
+ "63ae23c41aa546da589774ac20b745c4",
+ /* pk_seed */
+ "0d794777914c99766827f0f09ca972be",
+ /* expected_sk */
+ "173d04c938c1c36bf289c3c022d04b1463ae23c41aa546da589774ac20b7" +
+ "45c40d794777914c99766827f0f09ca972be0162c10219d422adba1359e6" +
+ "aa65299c",
+ /* expected_pk */
+ "0d794777914c99766827f0f09ca972be0162c10219d422adba1359e6aa65" +
+ "299c"),
+ new KatVector(SlhDsa.SLH_DSA_SHA2_128F,
+ "SLH-DSA-SHA2-128f",
+ /* sk_seed */
+ "c42bcb3b5a6f331f5cce899253c6d9e2",
+ /* sk_prf */
+ "9ff2b7ead7a04bab1794db8cc659c3b4",
+ /* pk_seed */
+ "a868f1bd5debc12d4c9fad66aabd0a94",
+ /* expected_sk */
+ "c42bcb3b5a6f331f5cce899253c6d9e29ff2b7ead7a04bab1794db8cc659" +
+ "c3b4a868f1bd5debc12d4c9fad66aabd0a94b546df247be4c457f3d467cd" +
+ "fcfabd39",
+ /* expected_pk */
+ "a868f1bd5debc12d4c9fad66aabd0a94b546df247be4c457f3d467cdfcfa" +
+ "bd39"),
+ new KatVector(SlhDsa.SLH_DSA_SHA2_192S,
+ "SLH-DSA-SHA2-192s",
+ /* sk_seed */
+ "040266529c1864088925506c20a624a2b6d50cd77c1c6f0d",
+ /* sk_prf */
+ "2841150ae8157512ef34a343ffea77ff7d9e814b45a8b414",
+ /* pk_seed */
+ "64462665f4202886206a8f632267186ca6a1cad08a2b9a86",
+ /* expected_sk */
+ "040266529c1864088925506c20a624a2b6d50cd77c1c6f0d2841150ae815" +
+ "7512ef34a343ffea77ff7d9e814b45a8b41464462665f4202886206a8f63" +
+ "2267186ca6a1cad08a2b9a862c6a7bc4ac4aaa84accef60d529f0311274f" +
+ "205e8da642c9",
+ /* expected_pk */
+ "64462665f4202886206a8f632267186ca6a1cad08a2b9a862c6a7bc4ac4a" +
+ "aa84accef60d529f0311274f205e8da642c9"),
+ new KatVector(SlhDsa.SLH_DSA_SHA2_192F,
+ "SLH-DSA-SHA2-192f",
+ /* sk_seed */
+ "a021b4b9d6dee168722bc10225e50a946642af630c3c7c7d",
+ /* sk_prf */
+ "69e3a40ba09df2ac165b792a07f064ac5fc28d8c99a580f4",
+ /* pk_seed */
+ "ee4823d09e79854706daa80ae3179b5bc8c2e9409d6328a3",
+ /* expected_sk */
+ "a021b4b9d6dee168722bc10225e50a946642af630c3c7c7d69e3a40ba09d" +
+ "f2ac165b792a07f064ac5fc28d8c99a580f4ee4823d09e79854706daa80a" +
+ "e3179b5bc8c2e9409d6328a33577fd584bc0784c559cdb2437a46f7f753c" +
+ "336369419acf",
+ /* expected_pk */
+ "ee4823d09e79854706daa80ae3179b5bc8c2e9409d6328a33577fd584bc0" +
+ "784c559cdb2437a46f7f753c336369419acf"),
+ new KatVector(SlhDsa.SLH_DSA_SHA2_256S,
+ "SLH-DSA-SHA2-256s",
+ /* sk_seed */
+ "fcbf36a9807b30697be063a5105e091b412a391dd39e1326eba23cbd4096" +
+ "ca77",
+ /* sk_prf */
+ "ef4121c08dd71be913572f1f91e57d0acbcd5cec28539ac275832bbaa6c1" +
+ "1081",
+ /* pk_seed */
+ "a0b4f5549ebcadb951dc2e512c76b0620d8fb8100b4ee886ef8784780d52" +
+ "a254",
+ /* expected_sk */
+ "fcbf36a9807b30697be063a5105e091b412a391dd39e1326eba23cbd4096" +
+ "ca77ef4121c08dd71be913572f1f91e57d0acbcd5cec28539ac275832bba" +
+ "a6c11081a0b4f5549ebcadb951dc2e512c76b0620d8fb8100b4ee886ef87" +
+ "84780d52a2543e57ab494d47068ffee4b8244aad6f19cdf94a2172bd134a" +
+ "15a6b5f298d5a80e",
+ /* expected_pk */
+ "a0b4f5549ebcadb951dc2e512c76b0620d8fb8100b4ee886ef8784780d52" +
+ "a2543e57ab494d47068ffee4b8244aad6f19cdf94a2172bd134a15a6b5f2" +
+ "98d5a80e"),
+ new KatVector(SlhDsa.SLH_DSA_SHA2_256F,
+ "SLH-DSA-SHA2-256f",
+ /* sk_seed */
+ "18523702a0fe2c9e488948b127185bab93d3f02c3d7c23a1b379f762de05" +
+ "09e5",
+ /* sk_prf */
+ "6ab0d9f93540bd809d1d2e8a050440aa81e853750470e2b00c959dbd3be4" +
+ "0e2b",
+ /* pk_seed */
+ "d7125f5d00ba47f1fc8d4c32c2f57c444bd384d7ce770bc50dd5980c1d12" +
+ "64d0",
+ /* expected_sk */
+ "18523702a0fe2c9e488948b127185bab93d3f02c3d7c23a1b379f762de05" +
+ "09e56ab0d9f93540bd809d1d2e8a050440aa81e853750470e2b00c959dbd" +
+ "3be40e2bd7125f5d00ba47f1fc8d4c32c2f57c444bd384d7ce770bc50dd5" +
+ "980c1d1264d00ad5197ffcbaafe11b1e413f26adb1504ce1c3f5c40c1dcd" +
+ "a14e99fd126d5b81",
+ /* expected_pk */
+ "d7125f5d00ba47f1fc8d4c32c2f57c444bd384d7ce770bc50dd5980c1d12" +
+ "64d00ad5197ffcbaafe11b1e413f26adb1504ce1c3f5c40c1dcda14e99fd" +
+ "126d5b81"),
+ new KatVector(SlhDsa.SLH_DSA_SHAKE_128F,
+ "SLH-DSA-SHAKE-128f",
+ /* sk_seed */
+ "3956ab391b4d22fc907af0740326d061",
+ /* sk_prf */
+ "ab0eb206436f2b86ebe086d77739b3e4",
+ /* pk_seed */
+ "56505c229f4e7fa6b201714c7dcc9da3",
+ /* expected_sk */
+ "3956ab391b4d22fc907af0740326d061ab0eb206436f2b86ebe086d77739" +
+ "b3e456505c229f4e7fa6b201714c7dcc9da366578f1f24c3fe371c97c14c" +
+ "e0e79cdc",
+ /* expected_pk */
+ "56505c229f4e7fa6b201714c7dcc9da366578f1f24c3fe371c97c14ce0e7" +
+ "9cdc"),
+ new KatVector(SlhDsa.SLH_DSA_SHAKE_192S,
+ "SLH-DSA-SHAKE-192s",
+ /* sk_seed */
+ "8732621860e9a6e1887be55f7af692b98eb4c10b2599f94a",
+ /* sk_prf */
+ "d5cc9d6470d8b21136158e8b1710f1fbe03eced37ed4ac68",
+ /* pk_seed */
+ "53fc64d46d7e1653ebbb36ed5fbc12c6e7cef3cb756482c8",
+ /* expected_sk */
+ "8732621860e9a6e1887be55f7af692b98eb4c10b2599f94ad5cc9d6470d8" +
+ "b21136158e8b1710f1fbe03eced37ed4ac6853fc64d46d7e1653ebbb36ed" +
+ "5fbc12c6e7cef3cb756482c8c620452e864e8497e1b38a7b04449219acd9" +
+ "e4393f9c88ef",
+ /* expected_pk */
+ "53fc64d46d7e1653ebbb36ed5fbc12c6e7cef3cb756482c8c620452e864e" +
+ "8497e1b38a7b04449219acd9e4393f9c88ef"),
+ new KatVector(SlhDsa.SLH_DSA_SHAKE_192F,
+ "SLH-DSA-SHAKE-192f",
+ /* sk_seed */
+ "fb7a2c2c75ce6c96b5f4328e0ab300476fc6f864cb5b0b99",
+ /* sk_prf */
+ "990ecb726ca822a4e3652dd92ec0aab7637ea41c0482ae28",
+ /* pk_seed */
+ "68dcc671e3534f81a352c275b6a25f906d2ed0ff62b8b4e3",
+ /* expected_sk */
+ "fb7a2c2c75ce6c96b5f4328e0ab300476fc6f864cb5b0b99990ecb726ca8" +
+ "22a4e3652dd92ec0aab7637ea41c0482ae2868dcc671e3534f81a352c275" +
+ "b6a25f906d2ed0ff62b8b4e398f1a9876cb082a48e9ae2c862b289486a39" +
+ "25cefc6ff4be",
+ /* expected_pk */
+ "68dcc671e3534f81a352c275b6a25f906d2ed0ff62b8b4e398f1a9876cb0" +
+ "82a48e9ae2c862b289486a3925cefc6ff4be"),
+ new KatVector(SlhDsa.SLH_DSA_SHAKE_256S,
+ "SLH-DSA-SHAKE-256s",
+ /* sk_seed */
+ "e440e39644a11a6a58e850c09c8f03c273e465237f3bef7c58de62281e67" +
+ "6cea",
+ /* sk_prf */
+ "99c199c00db30f8499a61b5b9dc8a361725f6ae80e97037176f408c30b38" +
+ "844d",
+ /* pk_seed */
+ "d7b5e755b4879fde3288a21af3e32fbb006fd9b8bc2b180eb9b0d82c9f31" +
+ "57af",
+ /* expected_sk */
+ "e440e39644a11a6a58e850c09c8f03c273e465237f3bef7c58de62281e67" +
+ "6cea99c199c00db30f8499a61b5b9dc8a361725f6ae80e97037176f408c3" +
+ "0b38844dd7b5e755b4879fde3288a21af3e32fbb006fd9b8bc2b180eb9b0" +
+ "d82c9f3157af02acd6b3198ee1c9fe9afe61fd86d1e0877ad9061980b57b" +
+ "178ce27191d8eb1b",
+ /* expected_pk */
+ "d7b5e755b4879fde3288a21af3e32fbb006fd9b8bc2b180eb9b0d82c9f31" +
+ "57af02acd6b3198ee1c9fe9afe61fd86d1e0877ad9061980b57b178ce271" +
+ "91d8eb1b"),
+ new KatVector(SlhDsa.SLH_DSA_SHAKE_256F,
+ "SLH-DSA-SHAKE-256f",
+ /* sk_seed */
+ "2ac9403858d186b172edd8df9c78a11449893681487d3af0dad0ec341e8a" +
+ "ca48",
+ /* sk_prf */
+ "afa2771bae6c17dd6f77b4e3808b05f56f31b8f4128df2ccb677f0283cfb" +
+ "18da",
+ /* pk_seed */
+ "559bc883105e8ba0264648b532626155f87edb4bedcfc12a24204d3b696d" +
+ "5370",
+ /* expected_sk */
+ "2ac9403858d186b172edd8df9c78a11449893681487d3af0dad0ec341e8a" +
+ "ca48afa2771bae6c17dd6f77b4e3808b05f56f31b8f4128df2ccb677f028" +
+ "3cfb18da559bc883105e8ba0264648b532626155f87edb4bedcfc12a2420" +
+ "4d3b696d53707a158ff5d30e3428183a3b3a96a0e4a341a2a16e5a6226af" +
+ "374d1efb39a35df6",
+ /* expected_pk */
+ "559bc883105e8ba0264648b532626155f87edb4bedcfc12a24204d3b696d" +
+ "53707a158ff5d30e3428183a3b3a96a0e4a341a2a16e5a6226af374d1efb" +
+ "39a35df6"),
+ };
+
+ private static byte[] hexToBytes(String s) {
+ int len = s.length();
+ byte[] out = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ out[i / 2] = (byte) Integer.parseInt(
+ s.substring(i, i + 2), 16);
+ }
+ return out;
+ }
+
+ @Test
+ public void keyGenKat() {
+ assumeEnabled();
+
+ for (KatVector v : KAT_VECTORS) {
+ if (!isAvailable(v.param)) {
+ continue;
+ }
+ SlhDsa key = new SlhDsa(v.param);
+ try {
+ key.makeKeyWithSeeds(v.skSeed, v.skPrf, v.pkSeed);
+ assertArrayEquals("priv KAT, " + v.name,
+ v.expectedSk, key.exportPrivateKey());
+ assertArrayEquals("pub KAT, " + v.name,
+ v.expectedPk, key.exportPublicKey());
+ }
+ catch (WolfCryptException e) {
+ skipIfNotCompiledIn(e);
+ }
+ finally {
+ key.releaseNativeStruct();
+ }
+ }
+ }
+
+ /* ACVP SLH-DSA sigGen (FIPS 205) known-answer vectors: deterministic,
+ * external-interface, pure-sign cases from the NIST ACVP-Server
+ * reference vectors (SLH-DSA-sigGen-FIPS205 internalProjection.json).
+ * One vector per hash family independently validates the sign/verify */
+ private static final class SigKatVector {
+ final int param;
+ final String name;
+ final byte[] sk;
+ final byte[] pk;
+ final byte[] ctx;
+ final byte[] msg;
+ final byte[] expectedSig;
+ SigKatVector(int param, String name, String sk, String pk,
+ String ctx, String msg, String expectedSig) {
+ this.param = param;
+ this.name = name;
+ this.sk = hexToBytes(sk);
+ this.pk = hexToBytes(pk);
+ this.ctx = hexToBytes(ctx);
+ this.msg = hexToBytes(msg);
+ this.expectedSig = hexToBytes(expectedSig);
+ }
+ }
+
+ private static final SigKatVector[] SIG_KAT_VECTORS = {
+ /* SLH-DSA-SHA2-128s, ACVP tgId 19 tcId 161 */
+ new SigKatVector(SlhDsa.SLH_DSA_SHA2_128S, "SLH-DSA-SHA2-128s",
+ /* sk */
+ "9AE2F36B1EB8295ACDAA5589D996788F06977D16AC51524D770152E02DE29C8A"
+ + "DB30F66D17E197683997000BC98342AF"
+ + "9B0862379CA9F32EDB1A2EF3A9262207",
+ /* pk */
+ "DB30F66D17E197683997000BC98342AF9B0862379CA9F32EDB1A2EF3A9262207",
+ /* context */
+ "0610D63CCA608F79C617F1C311FB4CF35D63982536A41B2C305A21D56641AD21"
+ + "3B9B0253E8518F46642033680F25F91D936C184F78F6170864BE8E59A6F399FA"
+ + "7F01E80B19DF5F76B3111C4EAC7FEBBEB4C7C9FF3B2C2324A61B3348C8425781"
+ + "6EE0E8FBE3AEE55C6C547C9B1ABE239707569CFD7408DA68FDCCB9FE3BCBCA2F"
+ + "AC370CCF17F31551C53E07169520F0601A6E14937204B87BB0FFEFF8FFAB9A73"
+ + "6A12BC0B1E3D051CE90F022D848A68ECC5B8AAD63F354269422B0D1D0FFE60C8"
+ + "6B8BA0FD0DB518C44454C8B46DFAA7BD1DFEF75F7305FF0B10CAC037024A70B1"
+ + "C470282B4885EBBD181EDB3F4052330362106780B0243893B7E983F97230E3",
+ /* message */
+ "20",
+ /* signature */
+ "630DA8596A4D35420375671D16319B59664A38D8CEF78D7EAC30BB9BBC84B78C"
+ + "98218A5D5AC691B242680DA056553DDF18571A55054BA00C09F901FBFEDC3DA3"
+ + "F7A9282EF455426F86DA132D6EE885A1DBB7FE796F5DBF0AE979166B93948FC3"
+ + "485814E9CB6A9C656569F8F7ECD165B7E774CCD20B7EF1D052A56D1476C5AD84"
+ + "0280D82E43385541CD86C89DDDEDB955E89C31D696500287C73E70D5CF894021"
+ + "31C25F9D07B68EF6934DB8D13324E35BC1B0E59A45FB33A87D71A8154B2F92CE"
+ + "EBD7E095855BD38C11E3AD571F27FE60F3D37AEFF5E9F05A39AFB8FD0156C221"
+ + "BC40B847FBF7635D94C33284D125A379DB186206AFBFB6C1DFBB8AAE060D05DE"
+ + "9C9BBC2652A506DFF01AE980057AD5513A45FAEDD058C4509E9C3737D8EB9155"
+ + "BFC460AEA87F36EAD99B9A6B3DA5362D1035FF87A0E421611B225AA07DD4D5BB"
+ + "E90D8272258723F29867481276F01789A94C617B22A8C5EE7A86D526B6A9BA49"
+ + "24E9BEC3C0BC993ABB44032B61F76A37DCC17B150E5FF474B499582068203817"
+ + "8C8DDB563058DD15985D27A4FFC9BB179465443449E6CAA7A9E90E9AE061F3B1"
+ + "1B9285951F9517B4F31F1AB05E7EAB67997DE3F03F3381536CA7420235316B9D"
+ + "9C9BB634469231974A0FD690C490E3F8D00160195CC9757BC45A3ACB84DEB975"
+ + "11EC7AACD7219AEC8183188C4167ACD9AC00B49B9843C406253F49B5A317FD77"
+ + "1A6DD906873357C7E077FBCFBD7D3FC5FEC42D3476F94EB8B626B1F26AE13CB1"
+ + "009C6A719A5FED0E1E9B4E15C532676C3A624C6CAA6036C9CA145D25286274C9"
+ + "4FDD172590D82E4858D3FDD14B77AE1CD595DC3CBEE6AEAAF00E748E630178C7"
+ + "A489F1B201199F221C3F2DDFD77C46F08FCBA979F0E4AD6916E92D1BDE94C998"
+ + "CDB71CB0233C7FDC264DA44E2839DF1863819C772CDE146724716935CF1044F0"
+ + "A2DD234E7A73FB7EE0456C085B6495B441783715F27BEC7149B2B865404B8128"
+ + "93A22BACD975F0DE66F9821CFE3E066364F5A22BE584EE923199043396955F2A"
+ + "83443E01EED644F87F10AA9EA1808B7BE488355883A7B7A6C0A9F505D7475277"
+ + "43E22337170015F65FF78705AD752DA841530D7DF480605C2B3792AB5ECDA2F3"
+ + "49E9A62130972D650BA84DB47554780B5D4C68ABD7098546BBAE74DB22152AD4"
+ + "8E4B68D174CDE9729AB05AD114B885FC02FD1D7EB3988C275FFF666352FFD193"
+ + "4FFD39A40182B7A0939D7333BE7C06311E060F557C12B77E20B81D38E78E105F"
+ + "A58A11F600B8B6DFA15850BEDF499466BDF36CC4BA76D9F7946FC2F066F538C7"
+ + "4DCD4AEDDE1E45D6F19B9863B1E3A693B5C82AA72705EFE500DE8C58032B7D96"
+ + "4E2740C788D4CADBA2E1804A02C12CB048DCC7A057B26AD3645345B2F98CE171"
+ + "0AD8678F99B84790D531DC154D8EF8F67EAFDD3822D2EC79D57EC916FFD76650"
+ + "509597CC4524FEEEA91A14DEF32D144E5E53CF8DC204ECE0FB36E29B056151F6"
+ + "1B4348EE4C1C62F95B0243EF6409EC4736DBDC310A7B9B27231715D2AFCFE541"
+ + "8519D46B7E1F80D863269216E8E19A2716886495A9009634BC371BFD4F853D14"
+ + "5020CB6DEDC1FDB151AA3FDE7C6A214BEF76E0C68DA9C904FA0D741080ED2F88"
+ + "612F6D2A3D64D379F3CADC0BA9EB6E2381E9FD96BD0299BC202C6721A7ABC6C4"
+ + "A4B9F4ED9C7D6F9A2E8E9D8C6E53FAB5AFDE9FE829861A34C234E627133D0556"
+ + "F6CF5C14C2F6852C58FBE930CFC5CC002AE65F891CF46CA5B77030C7915B546F"
+ + "0283106F4816AA1CC5EC56CAC04F55D1A67FB4C241AF72DE6A93EB45FC5B615B"
+ + "A1DACDF6654C1FFC83A887E64ADC78E05D2EA08F8F0718135742FF2E014EA303"
+ + "ABECE2D4E5514FB3E60FA32C779AF35915EB9B67C9491AD4970194636907DB36"
+ + "93BA597780D4F32068D44CA2A2E4667EA0F8E7C02AED87BBE75A43F8E00D6429"
+ + "3A9FD631B11FA427C2FE030393617C9956209D59EAD9D39F259F606D67AE011C"
+ + "3E6B7FDB7E003EA41A4EA65C634F9B1C9551EB807D4D9715F17B6FA5AF825638"
+ + "2DD1E0BB562E3DAB96A956C66C6D13341D735F8FF46871FB8B257D04A57E0848"
+ + "E7C31E421815CD1E43CD9AC195BE17DB0DC6D83DA9C3D905F79C37F39409F43A"
+ + "1489205F8FCD16CDE3A08EC7A3ECE4F9976A8A6C42480C4006D57C5574F9D886"
+ + "638D9CCC95941F120885BE84E02FB7F205C5233A8F1B0F0D015E43BA7CC4BF92"
+ + "02C063988199C1F23B3029826F84E3006E42003673F1D649E4369A5A665CCAFF"
+ + "96AF3EA2752B901B58142C5F072AF21A632EB34865326FC950154E85E2433CC7"
+ + "A159F350D84E7129D33D531A044048EFC7FA0359551F4999A46F9330F328F01C"
+ + "8B65225600C4AEF8816250F4A3A50B9C408A00C738C4B590B30F2A6CB5591906"
+ + "EE7D600FC9397E2506C00B405F0B904E719838FEB4D0B667B3D7C0E4A412321E"
+ + "D5F45CA970C1E2A57C951AD19DEE6E546A6F3320F383AA22CAC9C5DA3C84CC35"
+ + "DA69190D1942A2403048FC10D169D77928031DEF4E3D5AF774847B712AA7A7D6"
+ + "61A98DD9B818B26AE555D4592047C4B7FC206E67D8995E00EC3D1B3249C61EF4"
+ + "A68F92D757BC343834EAE0AC0D29F79D5EA62BBE637C249930C013766C3B1B64"
+ + "CCC7BCA32483C6D01477F6C84D9021EB8EFB84FDD6C1293C9B031D3B58042D16"
+ + "F215F01CDD8A987878FF9D7C148274826896B3A887058750D2D69797FDDDE7B6"
+ + "6B710132CB731D9F5CE0F9280E0212E5F80E2B3C774A23C02BAF92F920A5F3A5"
+ + "2407C11DB710BB3B56CBE3E788E0C089A414E82F4C01C5816E17A595318240E7"
+ + "88760E7264A849BA9DA2151DCE811EF269F2415D4FE5B53274096D7434117B97"
+ + "8F0EB32CF379135DC0AF7B5972D2CA88D1DA887C62DA577BEC4675284FFFB510"
+ + "083D6DA8996BFCF0859DC039EBAFE72D173B1BAA5CB93FD094DEB1A1F97BB0C9"
+ + "AA31CDEAD4FE7286DD126FE67E5F696137CAF3B33B7F66DFB1544C048D943759"
+ + "B019DF309DCFEF7725F704FB0DF370799679A60FCDA9BBC47C8D55EC20B0D653"
+ + "FC5C11ED1AA65211D5F7C80C9C9AD7468A58261DA31D6EE2E6407515D74F099B"
+ + "DCC3D63A76E3B5B008DF13C4FC0CC7753283393C5252327FB9AA2027FF85B3F4"
+ + "3E94F94538F9ABE360FB38A58C060B19B1D60A9D98396614E980EBB957044DAB"
+ + "D0400E4D11A4C31E41B8B8BABFC9247BCF12DE04A4F18F6F89AEC46A39CEF38A"
+ + "69D0BA305A1D75F72312BA22D8A1B1C99D6EDB37E1EF9EFFB188138352272110"
+ + "9D25A6B16A420CA08B0A7FF45A07484698A440FE425DE405D4F46B0F2326DDA7"
+ + "3BF98DD5EC40E824172078ECF4B6DA0C77CE4BD485258CBD47A9FEF9DB7A6F0E"
+ + "963EE331721E816A5F9EDA8D8ED81EEB32B62A429877253B92360EB67DDE3AEC"
+ + "11196E81A0FC691B58326BBF9143B8FA9BEFBC110F7B6118E334DB6E5D26BA64"
+ + "E83E2ED57C49CD4070A0F37C5F38F857F6D78396CD3E1441FCCDC33D25BCA9FF"
+ + "29AC919DF1D2878968366A13F53878757B830A63A0C95EF1C6FB492B84134B34"
+ + "99E08C0867B877FF4D19741EAB55DE59E26C355ACED8030AA70C7652E4BA2458"
+ + "520089390EB17F2B322C30A6D130CDC5299F80EBF321B7D7F466E556116DED3B"
+ + "A6A68532118475B3626D6C07F9E6F2CC458038F81AD0A5406CAD82840B319B7C"
+ + "1ADD82E8C0BE84F84B6128981557535D2CBB2FE6C314924FC49AAF4AFFED6A26"
+ + "DADD14F1DE33F4F32AC145010739E063B32E4E24A62DBAFE33C5D07CDCA45F10"
+ + "4056140FDDC21987A7796515B786E61E9D0FB7CD75C79F2EFD607CCEA6C739ED"
+ + "04741ED08932F58208506A4B7A6066AD939EE4B900C161E4D5D1A25ED3FF9225"
+ + "0A33FF97E540CAF765F0E09035B665705DF1C5A2C57BF8A15AF1DF266C6511A8"
+ + "345A9C0BD84638EDF94DF1E3720F5C6615D9C7A6CF4B0B100D81F67727CAF552"
+ + "28A506BBA564B56259B0C3AACC04E1D2B71BF78A20468C35340F646B6423F7F3"
+ + "39FCCC245F229EE57E02CDE3CA78C29B5DB9FD937BD3F3E80BE45CF2A9518362"
+ + "7A5A09B83176AC7BF43A88CCEDD83289EE5734AA8738816B2EE7292296ACDED3"
+ + "CD5CE0E82495AD7C9AB9D75C17204F37EE6716F446DEB81D5720EC783D521CE3"
+ + "6D64E5ADEB161D4861F97F4993DE2A663A6EC46C1911286CA3411E4EDDD69228"
+ + "AF73984264AE65A78F1C3BCA1DEE7D86D4E65273973A9F988EDB2B267D6CF669"
+ + "264F5352579909DB3873F09C978A22B7452C4695B3C5D459C2E46309952C48A0"
+ + "97BD08EDC69BB20BD0DBA931C783A16AA501E21F65C5E52BDAB0FD2CCB21C824"
+ + "29D939A335EA43B17925A171FD101F5DBE397747304EB135441222B02EE5A712"
+ + "0F88655A96F68546DC6216499DF4FB61A2BB197FC25B3F615C6FACF9F87BC82A"
+ + "DA37EE09FBA6918C89808125C25362BD5B6CA6772FBE51568941F1E060D6229F"
+ + "E3EA1027A31B3A107050E8BC25A000035C1556A249761006DBB6C5D748386151"
+ + "E3995AB389171045D92AC9E747F69121B1930385D69D9CA0E78317AD58C1F2C3"
+ + "2C569488658AEFBC53175E1EC2FD349916F66F0C170549CF5A9F8E5531DD75A1"
+ + "4CCF096DAF2D7BE5424900B339580322B3C325A700BDA181725E9DBEF6CA53BE"
+ + "EC7C7A3EAB04FA99F1E1ABE930CA935BAAB07A146F4284C7E3808613E724E5ED"
+ + "7B23CEFC93B77848D7737B762FA662BA940E20B6533B9275E7250FD58ED6A898"
+ + "4F1949C8B4D9AD4D6EF144B6FF33D1ADB66F8F991DAE9FACB5F6690F61EEC081"
+ + "C6EAADF7208D3B80A1A72D411A955FE67528D70282E298908D00C116C653876C"
+ + "A18333AA8C8A68DF936DEC59D5B549F5690FCEAA0AA67B5D4F5E4ADAC1639412"
+ + "42784846476AAA1F3562C029383F87682CE78E10FF526861386E62E057744B2B"
+ + "5ADF349DA0E3D214EC038189D57649A68DBBED360AE31A2CCA9243E4D828568A"
+ + "7436FAD2536437662BBD4B2D5834D6EAF2437A02F5DAD641A6C9509A24D0E7B2"
+ + "4647159F6A1273DB0C6DBA847E6F4A6A6C99CD8E2160C2D1D23D6F95839908D8"
+ + "0DE30D2FCAD15EAD1B09A7DB183FC6F1E29730059B9FCB8727D8E7525DBE50F9"
+ + "AC6096074E1F640187F23B65CF29687F10D83D7104FA751E3D3DD96325CFF4DE"
+ + "5C27982265A0ABFAF550C7A0487C43A9B0B0DD8FCAFD72647AF912F56A5006BC"
+ + "09AF79DB6D75D920FB9DE0CA9F1193277E773F3013E6F1555B31F14E5D9FC215"
+ + "E4E058F0700D841F5FCD54E50D2D8AD0A0CE12C2F84161747F7C9DE084DE1B34"
+ + "EB1CD8CCCAAEE0D341A2E4BFF01FE36BF5D694D4C13CE80491C19C6A569B2588"
+ + "9BEFCEF12C368C98A4CCBAD04524845C24F09420A00AD72A5BE1C7D2F29B53E4"
+ + "4322461BB181B4A9E3965D37C3B287FB9786F9F479100EF7866AD7A1FF151318"
+ + "15583E98B985CDAC94565C7DF96C1685D35F4EF8A9CBF91B6FDA161FE6FB85B5"
+ + "E8978286C5CE496ED70B971EC43378973AD3C5BC8AFC676BC9E14E649E9B5B9F"
+ + "C36F1AA1C0345C93292B8DE236CEEA0A6AF20154F4F1648EC13E529E745B2D67"
+ + "DA8346B0875E2CA7C97EFB5195227E61F58AA8CC1F50AC766AA407C7E4567C65"
+ + "B8DA6456A9F62331A7F585A2BE52EBF92B4C5CB5DDDB5C65696E1CC0979ACD40"
+ + "6A69E63ECA476BACB413D91D39393704B6265194A279DE32D104FD32591B7CB8"
+ + "4FDA2668861282AC4BF10B0447C021D852B71431359D076E093B0244E608FCD4"
+ + "52F11A9E1378B57C0A567B4ECF6617769005440E02AD85ED1D28891C05729D8F"
+ + "AD8153A0E9540125CDD9B267DB0D6E754C3DE632C247F2204A53E9B8B969D04D"
+ + "0A02C9D83067A48000E5552E4645C897DCFE2422988A1A9ABEA8648B1BFD8AF6"
+ + "AB6DB484868CAEFA95498A1EEBA601578ACE45577693DBDE1B15431DAA1C052B"
+ + "F37F27EB966C3210DD80A75C0F99F132E6F56FF19ADED0EFB68604FFBF1B7596"
+ + "2C787020D4A479D801EE6BE2EAD8E1840080D3A20168AFDADD0CCF9EC2EAD44F"
+ + "84CA8BD0E5FCAB9A8C0AB80723D94C012C0CFA6DFA6ED13F6B956A142585CD09"
+ + "6B736B6765B1A39131A68F22B75503022DB74F6F3DE7C093787E7B327DEC202D"
+ + "BC5BBC05640032D6410B8CA4015B744B0EE22A1499181D43BF9195C7A5451CEB"
+ + "44FDAC2BBF0C7C05937C78ED8EC57BFB86DBA1A6D6EB1F30E6486DBAAD6C9BF9"
+ + "04D7D157B88CFC88563720C7B7521653566E7E0CF410F2C9C52BF5214684E94F"
+ + "0598393D67AA48747AB874D69D031E8BF33AF9CE8B4CE0A8D79BA16D6E83F1C6"
+ + "4B8D000BDCB1B003BB1B617026FE850E55549BB1A678990A5D5CF4300DCAF410"
+ + "3DA02ADA0959198F97030656D65F6C8759B0686F213C2DD78BACF47DC5E112A5"
+ + "7F68782ED118E2161EF28A1ACAB56ECFA396078BB652C8D0D279B11F50785A5F"
+ + "56729B4BEC7E2E8C24D2949E99CDFF59BC6C9D54E91879EDE6CF991537D963D4"
+ + "470B9D9B5C3496F8C7539D5575602B01C5A70788547760733E7E74B150875EFE"
+ + "A6EC747051217A0350AF6FF3792203BAC4E065E043367520C1C8C14AD5C25921"
+ + "E7D46AAC932F2137ED3C5DF51183C76D67B7F7689A11DBB1749DA843EC23939D"
+ + "35BAD4422A53D32D9413AB5CF5D36918E421F72DBD224A9471D2A14AEF5822F4"
+ + "0E4A6741D10960141332EFF7715F0962E8C21D70BBEAAE684A7B98F224581132"
+ + "7018EB8A409C8B3F9614415AC9AE4A4314FB295CC926B8FC26D278946A807B32"
+ + "619A44AE6CC8F875B3D747B6CEFD475C47750B5A2F1DEA3280084D128EADD126"
+ + "338E0E9A4D2340EC6665947EAE1B8F48A7B3280DB152919809D13F1BBA248107"
+ + "0C8FE619162F6202C8256DE6A0B68D8C6093C62279D4A4A23597B8D8846F54D8"
+ + "72C1B56DF62F9FCAB211ECFF7652521030C12FF04B1A40D2BAD37B277D3ADF15"
+ + "A993B483E6534C3CE8906274497AB2C83519F1DC77B6CC6E3219D25641BE9CFC"
+ + "5E51392AEEABCBCE66B1BA1C5B3A1066F1D3F5761710C46D58D3906A47457D72"
+ + "E1244FEDE14A3660840F6475901A1D391B9B4FBFAD925A1F620A94CE3B547B3C"
+ + "F5F82CC9878ADCB8F8419E6A5C4A332DBEDDC9F4771D302174B3531156FCF217"
+ + "1EE9F94CEE5BB8FEB3A333185F29C039D26620890ED3308C0D936F322EF54CE7"
+ + "208C8E3D26DF4FCF9A654EE9A2898F87023D4B907A8D0ADE8D61CACBA8A3EC67"
+ + "05FBE8B375F1168FB374EEF7ACC1F240017FF7EBDD68F44B664A2B3D4631D174"
+ + "C5B8249A4124902D747C6B42760A6D6CE2D127EC16FBF20607BABD64C03CADC6"
+ + "7ADC5C930B2A58F1FE678C8ABAC324546159B62446C07F1C958B4D9190935663"
+ + "68D3CB54AB973EFD1847634766AC690412BA8899953C6523787E6BF1E87A48DA"
+ + "C3D66CBA67D241B6953C6F29653E7E71B9AEE5E9C91BADA6EF95B5D52FD32ADD"
+ + "45D707FEA4C854C5B610A087621C5F28A54D87D9A36F4EF91D01AB129FF895AC"
+ + "370138A57DE6BA0FD1F7D659B062DBB58A744985190EBCE7B7BCF35EAE3FA587"
+ + "FF9C437AEF81CC194967030D5D099A895B3726BABDF283E5F9231D1C4667510D"
+ + "97D4A3C9E544EBED8EDC74CE0003077DD9E82267566D0907B614C32FC466BA5A"
+ + "DE473020F675AE6F3F23CEF15D49C5CAB979CA9F51FA59AD642D3F7DF5166946"
+ + "486B23680B26D1E756F8251D4A2CA8D3351B241C930DCA5833CCA9B16D6B214E"
+ + "9CBACC97BBA87B6BAFC160D68CA75ED872FA14325F078BD54B723B3DAA8DDF26"
+ + "D94DA0C18928BF2D9238047ECC1E83AD663889D4EB6D928321B499DB089A9D55"
+ + "359FBB5F08BC65B5B1646615AE6BAA9F93A60C2DCE8333384172578596480F2E"
+ + "5EF52DE483DFB647228FBB27297CB2FA1A8BBB44BFC912010374945388918C41"
+ + "E194230E88149F23EE5542E8478B39B4C24A0B235223A741B2976A86695346EE"
+ + "608C77EBD1287035E8DC0CED187BF1E8DD0FBE248665159800C8C23316B614A7"
+ + "FFC1CC7E07C56428D45A768BCC6A45E01D66CCE9D6044B80B6E77D5B9DCEA037"
+ + "1A1FDD63DA33194D28A777BDE4760D0BA21CE35590B76D7F2325DFD27B47614C"
+ + "366473EC8795276CBADC4167E7BA58934D4868F83E3F31EB0E00A9E50F58057F"
+ + "96E624FD25DBEC9899685ADC2B37232B139214E24A738E4D6CD92A8D8AD4B137"
+ + "3E76994E8B5E2F79E210A6147959A9B20F8BC3F956D2ADFACD253E5D7FC471FA"
+ + "DB5185160305F22C95C2AC6BAAEE4EBEF74F3FA8CFC505818377C311BEC78A57"
+ + "52CB9382B412FF13B79979856E84BEC1711E594EBB87AF3C1B28A0CEF3BB1678"
+ + "53E03A175E1AF9E5B21C868681F907CC282C86A4A2448A8B7EEB9F67E14F4078"
+ + "39C0913B26A1C8510C877645FD2974D7D0672E340E5F05F2FE44B4B49BF40380"
+ + "AE71FE54989AC378B41430FEDE46B00A94765D49ADC52A671F0921581ED7B116"
+ + "3C1E213D33A65294DDA46E96877A4A06601BFA99EAD37C848AD3A605D36E1A46"
+ + "80E194D850A3ED8B62DB9E5830FFD20405F440B8A35A98A114A3DF3EBDEFE952"
+ + "644FC82A50F3383B726B32683432172182E2F5D59C411A111B96548EFE225288"
+ + "5E7E694F8DB42E9F221223CE6EA52E7D2E078B09E4C3118A0C8FFDC46D92D1B8"
+ + "1BAA3E80B9FDE5103A3438D05006EF3FC486DD24BE9E594EF55FD7454D397E46"
+ + "8174B9D0BA12DB7E085B9485FAB5C16674499212863DD9D61AEC80E1A7DD4C5A"
+ + "9A9F09AF6A219E18F53E5479D9F9D2B864B61339E6367DFD1C252E669DC3AA60"
+ + "EE9A153A2BE13439E19A47CB67A35B1040C6E95333A021EE9CB2CEE20019DCE0"
+ + "38395B458E2C53617F1ACD502A94FDBDACA93B1EEA54491F076566E63D5EF590"
+ + "5FF9F2373B480D0F61052106F210D66674CF873E3C1491AF6CC6EA6AE22183E9"
+ + "A551E716DA33569AC1DE8D635899502E65A28363253D3B207A56D6C9BAF00B3C"
+ + "05AAB5E8CEA1D32F1D8A7FC42446647429F12ACE238B6965BA9AB4CE82DEE386"
+ + "A69CC656ED4B4F7AD890433FF404D075100CCBF95E6A48846F5920C44D5DEA63"
+ + "8AE0D78AE8C495DED6972560618259EB010F8C989A0F3AA3C2BD9987C5C332D4"
+ + "40124A180E699D20E2FA51532E67146678AB3B9B8B30E230B94424AABD92FE16"
+ + "8C8B8AFD2084C81AE4DCCDB8422C02CD1EE4CC71BDA137D0B48F660EF2E27398"
+ + "6FB69149447D16151B88C4F7B5390A44A5622DC952CC1E5AE4508DEA41FD5853"
+ + "C37DBA0D6CAF1322734D5D57024C112F01CA125B291898A84475E82B82C85767"
+ + "9949EF36DA607EFB59B1E4D68740FBE704C6C7917C62B4F087165B3BE84580B3"
+ + "FA687A15216A31553E997E3E5E9C00A8508B9F6C405C5C9918C801248FF8F067"
+ + "02DC78902B0D2DC5FD75A59FFD4EDA16F4B615E4819A9F3193963DA746ECA3BD"
+ + "22CD0F396D88D096A763D4A1538F6671E775DC80D41F81CA730510F35804F047"
+ + "C4CC53FF754C5248BDFEA9CA6E8F2A5B04099E1258DE9C0A1324AEDFA71EC3CE"
+ + "9CE7D728DE411C608A85DCF27BD51164AB5E84A2AFCA16621FBAE23EFF4C1190"
+ + "E4876CE260D73C7BCCAF23FED560FC5D90CA8A4B8D26438EBA160AC2F49059D6"
+ + "1FCD7870B204BF8A1E831C136C803631223E54B64C4EA1CCA3766447A16CC89D"
+ + "5EFBCD1D364F4B7B7A8EC7F374C3E2B4D824F2231DB95E98A766E00269C37DD8"
+ + "CD00A279F3B4CDA1C507FB2BE135AD70E9F889D515AB73FB9F5D85A98B5EE9A9"
+ + "37B99FE42C19F3FC2A355A01AC2D88C4BDB66E286E7F0577B6AA51D655B83295"
+ + "97E504BFFA12E822C3484932D72BC8E5B7BB6EE6835AD02ADD5E0D8CA238B5AC"
+ + "97E1CFA0416DE82DEABFAE846070DB2791CF32BC6429724E08B0A74D15A4FBD1"
+ + "54F212104E8E659B45074C06C8FB50EC4D036E78960CF62FC46B532EF68DA6AF"
+ + "03D8E8D52301171C6CEDE9EB153B04B54B93BE887A966B25A385CD837B7E2EAE"
+ + "C3B5EF7AC7AA06BD36E3C4A2DC3E4656D2E0AA8D4C132EF7CAC986BD11FEE0E1"
+ + "CBE8D60CC8E4417D665A4DEA1B780E78410FC9EB6036D1B959D81ED5E42F3840"
+ + "CBF76468E84CEFD53B26C016C8E2CB21D1A9B90171D32B6908C602F4D76B085F"
+ + "36E92DA8CDF97DB8C2AC96FB74A46401D5507C431E95B3A68DB014B8CD1D8932"
+ + "CD0002F0C0F4DB11D7FE499A7C71831F6D88253004B0B8A740205BAF10F3ABF5"
+ + "4A8FB76D16BF594EBA33E019E9A8E9821EFE1D0EF9C5C357EAE3F9A45E06D31B"
+ + "487BD00E5902D2CE3A6EE33F5FEA0B50E3F54DA53D8F6CF3351BE717F422CBCD"
+ + "B1D85570C47560C0E9721D0AD0E5E7BD6C4281B6C7A62F8C5D47BC0548CA14E2"
+ + "A8E5A5C537A115B0952806B80607A37ACD52CFDB94AFC71F7C132E9D8BE43E76"
+ + "52F2BAD885D5CA244416815288880BDA65369E102DAD6B3FD423F81EB339B2AB"
+ + "60D2015FD0D9FE9D027345FF2952EC6568E1B2578CFF7201ABB38F3E755AF173"
+ + "5EF46B0754AC7FB4FCD5209890454813DF8E0C26A4E4BE03E7D9464AB36168BB"
+ + "2A5CE5EF3C0A8450064200792BD15E969D97AAE987AC0B3F8BC5F23AD0C001DD"
+ + "7A2FB1D395F2DD228C2DA695D9079BE3754DA7F97EEE713EEEA20D7894C13FAB"
+ + "B6D9B914F7EB3BFFA096BC475996AE6C4D699362D7EEF5FCFE03ED90D6D6C5C0"
+ + "5666F328D285917230A934FB1C55EA43CC92B809FE19F21AC6B0D20E6B3A60F7"
+ + "132C68A39667087842A52AC84E9A3C2568F64F7C0F01E8D615199DAE1696C4F6"
+ + "B00AE3675EA888E789FC0A8A9C57F6F4B5291DE7A99B6678A1A694278D253149"
+ + "2A973FB388A125B02737650755AF045F3D74961FC6397D409C08322403D8219D"
+ + "1D44ED18A2282C2C726275AF1BB383625CC898E21A765E6DF1491ED37C44BEC7"
+ + "3DBED110D4E6E6A471E5C7727ECF078597F8323B2701A2332B33E503521BB789"
+ + "0BDC3EE8C62F6C4F99C74C17796F2FA3EB97EFCBB8DA74ED9C96F026A62C79DF"
+ + "6257C495589F646B925142529C963DD1B18C2AADE621D90A5247F08F43311385"
+ + "EFAB86D1C72C2444502CD5C25050FFB86B392505D533DE67485EEC57DFBAE94C"
+ + "96253E5C1EC2591D586E6C0633BBFCA80AB506210CD114C88BC89F86804FF965"
+ + "49AE5482833AA78E52BEBEC9009B26A85551802D55007A26D834EC9E72139782"
+ + "CDC1048CE222EE1D54B0D65AEC2CD62E3C27DA5C7967224FDA398B728270225C"
+ + "96A24FA6314A63A90E5554A707B5EBE4"),
+ /* SLH-DSA-SHAKE-128s, ACVP tgId 25 tcId 216 */
+ new SigKatVector(SlhDsa.SLH_DSA_SHAKE_128S, "SLH-DSA-SHAKE-128s",
+ /* sk */
+ "1CF0849EEF53F932A4DB4BC1FC236622EF5803E521B3EBE437827EF49961F888"
+ + "2E3A575B3E6136284D5ED0B96F5C5469"
+ + "3B0A6D8DC6E4ADAC66A546D3EEC7B9BC",
+ /* pk */
+ "2E3A575B3E6136284D5ED0B96F5C54693B0A6D8DC6E4ADAC66A546D3EEC7B9BC",
+ /* context */
+ "7E5DAB6CE1962B82363446D0297B41065B1E664E36D38ED69846F9C7EEF86360"
+ + "50CA14131640C2F1B34A48D299CB8FF968C95B42A142769371C2ACA488DFCC7E"
+ + "06E09CB874A9D0B7600791AC66FF9845EA81A6BB776940EC8289BDE6F723F945"
+ + "F6AFD23210797B0E84D2ACE22EF81C2FA9A4C78124C32DD812AC31EFB8548A0C"
+ + "E9C0A5832F41EAB103CB7FA4425298B478763B812FE3059C320174FA2B7EDA04"
+ + "77EE34C7AC95A5E526F7642A5F5FD986A75D6E925901FB1E885118D42710A968"
+ + "F5DF041CD94011E70995832B018EED4AD59B0BD4E7CFE37C0A0E08",
+ /* message */
+ "BD",
+ /* signature */
+ "0E6CFDA717823D6E2A571653EB7BBAAF125645D1FCA5F007611D463768C0E63E"
+ + "01D052F0872125F08F7191C5550CB832CECDB175E434642F15EB9A31E89C5F83"
+ + "1D25BB50630989E75798D2E35266E8630B4B65C163E4EE5253C6694E2047044C"
+ + "0F5A399C64E4DBC59EECCCA63D7C01BABAD2436F0A3974130035A670808E90B9"
+ + "625C979776B507E9038D5A2626BB2040C568461C3F768F5B25CAD213776E5140"
+ + "469BEB15980D628A1F2B3CA067E3C6B7106CED53E1F85E2B5BEDDCC495FB5DA4"
+ + "B18FA59B7D12567F5C118DC6BE526DE174E226C3D3A74B45F625CBBB28A609FB"
+ + "B77EC6B4DAADF373D46D6569747147AE01CC55F33E3200CBAB1402EC31686947"
+ + "33A3F6DDB37423DAD1FED017089DA8282577E24D25DD3FFBFD21351E6FB85D93"
+ + "F526F5D73714F1889B12FEF71E723A068669523428000AC29115485B338709D1"
+ + "579BA0B71BC8809410877F34894BAF5291B1ECEE49EDDB01D78D469346F9AFB3"
+ + "5FF608F19FDD76E069AA026E50EE93CC2058A0B2A2AC9D42E95227C97E782CD1"
+ + "AE57D6E0F4E30B4ACF7127814837922596AC9AFFDB4533E96BA51A97324662C8"
+ + "DAAB4E24D416A4BC40AA9191600EF7892EC4CEBD8C73C9E12A5FFBD1282EA295"
+ + "48D93EA519D2D1B83AFFB130DDC39AA8CF68A32B2DC8CC461E32C2795BA46025"
+ + "451EF19268145B88F78F12DF5227F252663BC46B9100B228EEDFDDFF2BB2A1B2"
+ + "3302C1D296B461464238461522666040ADD380BBC244DDD15FBA50EA26C90358"
+ + "E262F67DEABBA2FC35306E39CE21EBB5CCC2EC88A4882226075EF0A2CE34F89D"
+ + "37D1B79C1EC7C244D090417FC53AA134F63A50576888F1C4156C5C03936F7E28"
+ + "F37DD00FCFFD93160E9551B2CA138F44EF496F21F8BAB036223419420073C376"
+ + "5C2580452E167DCEA2A72428F11769EF8A814BCA1D1B5A47178B23DAB42E84B2"
+ + "D820013ECC99C48FC0E6655EC659FB1DD5BB95133575836879C7BC30A7C064AB"
+ + "82A929194722E54009BFD739E97DB8F65201B1E9FA80FFE9B2A8CF096DA9B545"
+ + "952277CDB7F8F4097D5F54913576E1483DD75696DC01852342B978CF3B0C4661"
+ + "6714DEEE9C46D45FC4779D5A94CF590D0A22A4CD6C027727F6FF21DB4E231737"
+ + "14ABC54DE9DC1B07D87CDB96A27189C4FEFFD0320455F825A6E309EC1DC4232C"
+ + "D36A2FD2EC59C3707B32A9E4AD5DA813CFFD41C97670752BFD0E6209244B9070"
+ + "D353FAE44D156B5E7201A1446DE1A7568CE340E3DC517912A8BE174FC79D6FB6"
+ + "9BC9957A607932377F569A167A1F002D071A6021B9200951B193451CF1A4559F"
+ + "FDF95C8EE567DB50A4DA1E027F0047A3992BC6986A4E5AD8F093C16E13E98510"
+ + "F876101AB1404B4450287E9AF605BA468C093C5199A1EB522B6CBFEDE084491C"
+ + "D33C811E8DBA036AE357A5252450B74E4121083246A085CB7967A9C339B6AD57"
+ + "32EA56D3B717F6113FDE127958FC3C926D5BED56A1E5B55B68F6174BB5031767"
+ + "9FCD364A7E93BC84DB717FB5842F029466E677D39372D1884171CC1B012D4810"
+ + "EAB84F15DE04D11AF28EBE587FD1140AC61510261A5A51C6AC0620D85710DFBA"
+ + "EC4B9FB0436F8946FD486181243DB9324A0C1D2FDDB919FF09901E38FF83A982"
+ + "5A8ACE0DA57E0A9D4D74BF4A0EDBBFE28EDAB8B335B5A362CC45D9664F0F3E01"
+ + "851258B2BCF7148B696FAD6A0663901F4DAA5D499A1EEFEF2927433DECA33D59"
+ + "C27CF651C963934F879341119CBB197A19A9B544C1C1549F5A53C6321208EBED"
+ + "B591B8CC5E3936B513AD94D7F45A96C03ECDB97C8536ACCE3FDEE9A7B11C17AB"
+ + "32DBD631DF1AD900A671E63E9A3516C4CEF59111E7D0CEC26DEB2D7B43A5DE31"
+ + "FC63CAE603B6E2A25C235C3F243830D4F0B085D00D020C7822FC623736857F1C"
+ + "1E94AB4A1B2CCAB7CF42FB6C495838EB00274872595E37FE95AA2C9183E9D634"
+ + "6F4ECC5BED22B3B93768503DB4B8CB6EEC0935A7E3455CF009260F911B3E6BDB"
+ + "FB2D450F62D03478DBD181F19720F0A90AE43D77F68C1C67E15D96D6FAD9299C"
+ + "4B8ED4C4E7A17BD45A87759CBF6278748D2F2BEB1ACFC2A19FD423D9DF6A6344"
+ + "52342CF148F9354DEE442CBD9FD9BAAA180DFB8A4D480FD94A0C8802DBA4057E"
+ + "C994CDCA0C5753419D1233569B07CF2563701BE7CEA0363D52E4A10A3DD5EED0"
+ + "0F59CDEB881D193AA153E2597C2C2EF038F0BB4F202D3634DF4AAD9143A5AFFC"
+ + "CC2832F0FE44312D73B76659F20EDFBD30CB27563728711D8BA262595CCD6B44"
+ + "887AD55239332FF500B0C1EACBBB71A8E194085F8CC5C2D3F13E039BDE34C60F"
+ + "D3F91AFC46D881E5017BE6F0B8E3395974E86EC35C8F377CBBCB37823C89287D"
+ + "574BD5B795D305771754008805668CD43AE32E1A7D280C38D311AE55ECCBAC37"
+ + "0B526AE165354DDCF6DD5001756B3565371FC88E1890119A055B04BE96ACEDF7"
+ + "CD61CB196DB51D71AB545CDFDFCFA24B8CCB70EE6B009CBD247EF7380B3ACFC5"
+ + "A8372E16B8596BC594EA74F1754F55F496E94EDD5FD18B706CCAE30B067CA286"
+ + "EE846C5E7F67C5F776D677E25997F8F1CE2937A55ED5F3A6E2FA04A414808531"
+ + "568FCBD3305BEAE2C2593EE8FDE297402988F953BD81E789E2A76BBCF14F5EB7"
+ + "6297AF788EC8250D37B47C7F41ABBD6237C1AB4B51F00D67E7FC53CED40B7FDC"
+ + "E6CB68072FB691F1EA815F0EC74744E2D63B568923BA0A268D55C99C6EF31B84"
+ + "756E80FEDEDD3FF0B063EB585F17615B3D6345F48653C0249D461EE22705DCBD"
+ + "BD161509B1545DD0E322D6CF352D548C34C807E38C83D0D5843FF2F1FEF1A0F0"
+ + "6E1F5B1FAE12B30B2539CD0B6B2EDFE8D7A8AA61633A2E94A3C0A1BA9902C6B4"
+ + "6971CC89CEE027BAAF5E8AE2AEA70555D52DE69E44AB29D8AD28C8FA4FC19827"
+ + "3791F718EBB64C2BE06DDEE8E0DA303FA9C89EEC021569C2EB2202274F5F3224"
+ + "56BE18BB2A6515490574D0E6BF9C91F9F93D9EA793842CCA0D22D001065C0F5E"
+ + "66FAF436DEA7584EF9D33B95776A6009F98D7AD183C17A7F116CB2F1C5FEEFE7"
+ + "2670F828839B48CC4D6CD7EB13BB559B933F322C249536C98D5F96DFDBC9D09D"
+ + "25C5950F55B1BF2682CFEC7546D64E534B9971C9D39B34C402DA9F61EF330BC6"
+ + "ED52EDD6CA1C7F87AD231CBBAC71AF3F7B044616C4510DCC1AE3090C35D70C34"
+ + "C908E19869EF289624C8B082CE200AC9B3EC9C28AFFC78A5D7357BC834241E60"
+ + "59DE6DDC97C44C8BCE588C00818E0825C6A0884E6995C7FA5D3FA0399EDDCBA4"
+ + "948223D9A7806C3A8CA536BDC5CB39371ABBA3785DCEEAAF63601D28EB5BE23A"
+ + "75DCAD49EBEA969B9A008644EAFC02686FF6924D69816E25F197F4A5A40C0C2A"
+ + "28DEF8AD65FB75B8B1723E05D3B8A4BE0C7A095F7A16D8903F06BD5B86EFDD72"
+ + "E94882321147A5E5CB6E620108B5D493330E29F9B6A44D6079D84D37E5854115"
+ + "F1061AE4102893B0FAD74F171F5EFD1FC97DFB0061DCE6E06C4EF06B64702B6F"
+ + "D2B1B221515176C629CFEABA9B289C889BEEEA04206B7C67B3D3F9366B04E10C"
+ + "BE42DF398F4D05C2A14804D71502681D83EC0B0C3265FC50ED61775F8077B904"
+ + "0F82E65A1BB6CFE0EE45181A9E2BC2C60018F75BC659EBE18BFF3CC007888E62"
+ + "14A5570D466CF883F6B81A7FD66891560B7CCB1FB6871E2E00C2B9D0402C62AB"
+ + "96DC5543ED5572BB890EA3259FE4B0B1B004C956A23BF2504109CAF56C0D44CC"
+ + "DCA9DD30EE8D2715B127355F69E733651508404F196073BB89804C8CF1295C35"
+ + "F34DDD924550516246B612B3078657729BC62E031C09E1301E6F48C5B77704D3"
+ + "1B6523CF15C906EABFC4750262DBEFDE07E5D5EE39AA5E4F217A57DB60F68025"
+ + "5832C19DB9FD8EECE16CCEB29899657967F494EAFBA3F563C97CAEB302B4E547"
+ + "5307368675A57B40258B4100BBE035D8ACFD9EDB75FCB2BBDB235E302994892D"
+ + "D7D8DBA28E34941C4055ED76B47A195FF69E2721CCE66B68484EF169FD38D30F"
+ + "F2896A64C04A6B9048922E59603B9F00D59FAE799B6C05B5A61EA805C6835481"
+ + "C51D2CFF23D3011F7FE8E7D5074120DC5AA084473D74C873BC104111DB37C693"
+ + "47684D977CB99C366677AD3EB43AE167A59ED32A85CF3391388A657F2B47252D"
+ + "17E3DAE7853F53EA937FE399578E17A79C7457EA713F3B359EDE7A428EC12092"
+ + "7DA9D36EDF3699C93148C343DA7876060BEAFA9448A389CBDDBA47C0BF0116A3"
+ + "B67AFD546F375DFABD2C4E6DB50D8826D2642EC57EFF9D3024D7419AC917F5AB"
+ + "F1785819FD816F3379BDA53A996F6A6716D61F045C4766C2427D174723B2AD0E"
+ + "D4FC39E12068A3CCB423F7992046114F74A1B8ADC10579AABF9DDD9AE29CFDD6"
+ + "60CDAC11AE86AD4531F09FD482E8DA59146FF3D979FB9C02CF1079C6AF76926A"
+ + "2607AFF1B2DE8DADA580CF2B1C27386B2C9AAC7410CEC74BB7E61BC67B9F6918"
+ + "3D26896559748D0A42013BC1E55901F5864EEAC6F6FF4225699D663D208866C7"
+ + "267AF56E7A08A6867D15FBA1A10186B3867360EB036004A49BF5BF48587D4C94"
+ + "7A53F7FCB6834C851B07E69EDE884530BA39BFDA8417F1B596F908ADA78B92ED"
+ + "197C780B0D94DA850AE22C07467DB571F10756058A833CA66CEB5FA0CBA507E0"
+ + "3375C6500A2D1CBAF4AD02D76A6078CCFB64A7EE59BE0C6FBCF0401CE1245AB3"
+ + "7C53D73EE7FCA226976C14F83D8646B39F453B74048579AF327DF5E2DB3C110E"
+ + "E85F870BB6A43C85834DDEA234CEC696737BDC28C30F6010E06B350E47B4E0C2"
+ + "2E3294F386BF234C4AD80E5C590FB8CB4957F7C56E213982BC1FF17D62AC5332"
+ + "EC12C6572165BEFC470D8BBF4E683FD8D1BAED922C1913A8FD7B2AD53F6CB50C"
+ + "63ADEA6DF9590023B6AAF61F0548FDC0DCCB549EA98BCAD648052D90C83D5AD5"
+ + "EB7F26934F7C18B0870BD8794BE38AFE0D7E676DEC23AEBDD9943D9D824E6438"
+ + "755E8BD64C05B6D629944573A0BE2150710A466F241175B2B32638AC25F8062E"
+ + "340EDA8785BBCFA986D2EE8B8048C865548A2401D213B21B1423018FD3AFD975"
+ + "2479240D40BEDB2923F8E412E8256CB155AAAC5084DBEC0A340AA57F03CE0CC8"
+ + "2CD6DC1A72D2A2EB290A062BB9A0C627B532333236C02561F4A2AC086BB6A41C"
+ + "EAC0BE53083CF33C1F628FC8925224E2A46931ACB39387252B95DA7D59C9B138"
+ + "73784FC9E406A6E33919124EDA7BFC160DFD72ECCA7226F8914062FF8BA508BF"
+ + "48A6DC36E37D8F66F91300DD38BF895A624DAF0C518152B94998592FCFC1BA27"
+ + "A66C10321A7024029D4A378FF8A98D781D67DA7600E944C1E7FDFB32DE11FF37"
+ + "561B7686663AE96EC53816E738ACB4D3E2ECF63B3475694BB3042D8800BA22A9"
+ + "AAAB4F29F5D2109D2BED1BBCC52814C7E81109356D6531531EF4E16855045184"
+ + "805B35C55CFB812C1266F2ACA066E31AE3A01E93AED4315E6458D5EACDBE1CEA"
+ + "B26CE460A94D5C324B21A9A13AE751786D57002510BCD6C9A2B4E053DC50286E"
+ + "F7CE9B014A885FB7EBA7D294DCA7AB525124112FDE4AFBBB306ACB83B549FA54"
+ + "41F51E958919F741A47F78E925F7FB62BBD1CA8999D482C6F4FACB4E3DC15945"
+ + "F2B6F0E838B2F471679BE050623FD4677FB97C9FF38811FC321EE45063981F8F"
+ + "060F6F52C959792EADE30EDFCF616D74C7F49C176675F7AAB63EB8208A95800E"
+ + "8C040EC19F55F8631499D2829E6B478C3020012F7B5361CA58A54A2314600CDE"
+ + "3984E18E83E7923152DF3E875578A90EC76F7F7E7B9390B11078790082F4D846"
+ + "5102AA75CBE35AEC1A24DB00E6F4AF8F3CB3B74128373F9CB9FA68C797C5784D"
+ + "778F350CA3923AE8DCF833DCA5F71B7B60722D0F0442ECC52D25B5FDA6CE8927"
+ + "BAA08925ED95E7CFDF5A17C31661E8A26524B8A595D55BA095AD7EE51198CA07"
+ + "7322F2AD24D42CC13C12D3C3BDC0402D8DA0A525A810E4190F2FBDD235C9093C"
+ + "4C4D48E56CA9F6B84642F01CCD453BE3AF735757626A8542F10B1B16BE360493"
+ + "BAD5CB0C0168E6CA313E0FD7534B2E59CC3A70BEF7F85D5D1FF026E724F951AC"
+ + "0B9A661093405898813D9B638BCD5F03930724DF8228119F880962EA5B9A7F56"
+ + "D38A5D4DD1169A28A89E9AFF8BE2A25A420B2EC448349EE73CACEC30DC1D08DD"
+ + "8CA34BA9E010939617CB8C90B915BA76D652B278CE95F3D32A031B438A92D3B2"
+ + "48773B10F01500E21F30CA46871493567C38171C25FF49C7D87344F48BB20E55"
+ + "0B25BB026F16F46C432CF21E58B72F435A6F55EA66D0A0792186569041965E04"
+ + "1F80EA30B831632F932D5216AF284804C8ABE6E8583B0BF56A72E4E01766C80B"
+ + "3D321A9AAD3B500463D3334B6B9829138B8A6002E175FC950C00B93FC6583C1A"
+ + "4450FA5EA86C7704A31E6A31FC75571A912495D157F75A7D18AABC193AD325D7"
+ + "6A4BF668F4E3095FACC7EE6854AD68CB7513B4F75CC44B2DFB8F6B9B333AB646"
+ + "3FA62B50C48A1B287D8574915FE5479AC47B8C334C604AB8948A9CE0A713C126"
+ + "0D6FC38D74C68C46FB73E2B999FD4E1B58E1F2B25242A88C719D90492F14A13F"
+ + "701CCC2DC08D34A9767AFDC6439B1DB04B10FF53567FE01BCB78D69AC004791A"
+ + "4E8261713C9E0ADB2447A3B767DAE1F7C772A880A61D267A85280046ADC4D182"
+ + "55194BFC2FD0B319816481C920EB1DB6C9AF488D553DB5760E2AEF22CA2547D5"
+ + "FCD19453AE38C70A7B8BCC28846298D2B50DF8DB984075343BEB8C6A9FE1803F"
+ + "2CA19D7EF9E8456BFC6D47509880F89FC9E421FFA2F210B70887C43100ACFA34"
+ + "AABB4FFC75892C118619D3CD97A34247E13ADFCDAF77C93ABE29C38708DC8D53"
+ + "4E3A9C986548ABE220F4DD1232F4C860B10E84C10A81B07B1004D15D35561C7F"
+ + "84A76E19F0067C5607F5A89F232086B3A5CA2A0090CB69535F9B143B279E5805"
+ + "B09149B8BFBA0A83EB461AD18CED18004B8C48104F3FC659FE4A0A2585870311"
+ + "5127CF520DA137719675BF3FCD2A8C7CBAADAF70350D986FADB76A56D4ECF7C5"
+ + "A7D004CB5A365B43E23106B915532B4FAAE23E59AC679350248A951342D7F724"
+ + "7985D5FE64A76CA135E50B02BE790AFDCD6046C63F7785246BDAA1C38CE41713"
+ + "374B1FD26805F1736013F4BBF62B42A91A6785B403E2279C3917EB2B12DCE31B"
+ + "A6AB398AE38067A1C08E44CC63829759A2C8C452132626F45860D09C6C6F6848"
+ + "461A8E8F8CEAAB7EAD50236519BA4978383DF9AE7853309E25A5F8A80A9B6F23"
+ + "0A98D479B02D8BA49A00FA8C68D68ED9977D845223BD6F3F15FD95590B490027"
+ + "10D976AD3B619154E40E6AF929AC1F83D9FE29FAEFC338B9D2068E78AF4E9FC2"
+ + "F756602EEF967B14FF18D0AFB9A41A14741C333BCEB29BB9D8131A619D616D87"
+ + "97BA9EC976897F3053D8725B515C947D031FE401647D935DE302222EE0E8D02F"
+ + "7817A199989A82A7CE97BF898B01EEF0E267D21E20C590E0F5DFDC85E2E63FE4"
+ + "F1CF19917B123988537706DDAC23985C8D33EAEEC15CA967CE4285C7D44B08E7"
+ + "105AE0F40A2D1C349FB02BCA90A93DDD383733B65426813AB52E3F1A9F256245"
+ + "6AC15620CFBF8B15E5B50ECC11D187D5EE9E51D68D54761059C0EAE7198A5F7A"
+ + "1D5DA6224A3DAA6292606A1919F620CCBF6BC99A5C07823B3DB769448FFA5868"
+ + "907413E3E10BEDAAFEA935BA220F0D92DD1A4902302155416D58CB251D96B227"
+ + "472B081BE1AFAA95DB4CC12726CE5BBF58663973B7ED62D68F17543DC29F5598"
+ + "2BC4E4F451E671D98710EFE7AD3EBAE9A78550A82DB70B04ED1C9D1358967541"
+ + "ADA5F24CD4FDAA6A8C1D067CAEE0FA841050FDBA10A41D5C6ACC46E88979BFE1"
+ + "420604BAB888F784B1A77DAE715E389A94915CA11B18A4476CFA44C4A7FC42E9"
+ + "D38D4AB5B040D5ACB329CD40C244F22BABFAF24D121BF23530BDA82C0328B252"
+ + "CA04902750DF076D558F7E9EE813F48ED3B9ECB5F817E957135A03820B7FBEA0"
+ + "BE06A24187E72F91D5AEFE7DFF525195F7698A7CCC5744AC50841CDB6F011817"
+ + "F5BFC743D67F1CF818F44E5D7BE9434050A8EBF0C250C000383AE5B6AD9A494A"
+ + "30E4C6C6DE31DED41F92A56CC94128B9754328F697B73CDC50DC3D50507FAF4C"
+ + "776B0B06A07BE33B15BBF4B5A6FBC9004D628D2AC60E483C7078E0B83F19BDE8"
+ + "120A6E8B8CA3A28AB7CCF8B973926348716C407A358D709F7C66F49B1CA1F822"
+ + "EFFFC2F4122355B28F3DC867F757EFC7A110A5ED567E48C26E64F4FEC248C8C2"
+ + "F7A9902A863DF58813152623D529E2799D363FD84AD121C6BE0A523EC7BB206D"
+ + "B9AD680663776516AB3EEBCE5F6E39D29DBFEC770DB2462FCBA82EB37D006128"
+ + "38E3FF29AF740F1B32AB66FF8571821DE04D485B74B1E1415FA448355114CCB3"
+ + "BCD52768251BBB00BABA8CA8B8E5B460231014F2A90FE167E600EC9119A1BD7A"
+ + "CA8336E7F53896C8E1F6550D5B7E5DDBFE54D9DB790AE8F5F4B8C2C0174F44B1"
+ + "4486C10E4BBF89A02817CBDD18DDC65AFF02C491834B9E0E502076DFB0EC96DF"
+ + "B3A72D6C840CA542D9710E8574BBF8A0D9E27CC2C8CB769A73E0435570CD05C1"
+ + "22E0BE360B9307DADD454981B629BB7DCA6AB3CD1873FB044000D9289DA790B4"
+ + "EB4F534DF2A2CF52FBE5775CCDBB5FF883552D2A8ED2D1EEAFDBFBC5730E9222"
+ + "F098A1EFE2DEA5AC6A2E37C71F0698156A679119BE8D0B8E4DAC078A06CF38B7"
+ + "B03EB503345A728D44751F91B7AF9573477E65F2D688616E9A6CD83873CAB0E4"
+ + "3E4979C916EA2092C4DEF3BDAD50BE50E2A388CD41EEE7E21D174A45E5A44274"
+ + "6A9FEEF689EF77DF9DA00EE283197686227CA060ED2EC1FE5FF364816FEF0B53"
+ + "0895C39DC5C58BA543AEC1FF0C4EE79049B2CB76B05546FA9125EFF612DC54BA"
+ + "602D539135F7A6EB2538CEC6DDBDDABF7A22E8FAFF0DCEBB4AD8B9411CC30918"
+ + "0B2BC641ABEE43B159BFEEF49A6C9904A042391821B84A1C852CD1868B4EF6F6"
+ + "2EE5173B49AEEFDF885F95FC0CFB496D8D41EB86F695498CBADEFD5D2DA15432"
+ + "B49F34E8375A7D357F73AE0984E022BEEFC2CBAA117B27DDB3E084D38260439F"
+ + "5438896B90021C8BD623AC3384DAEF133E585A31B1A87207FC27081685549600"
+ + "0C5C98A625AF1B1A3EA83649963AD8590663F6C73345F19F145C589D9D53010A"
+ + "669707D380660D28EC0E6BD06AA6DCE608B6305FA4FAC9A693FD4AD8B58D66D3"
+ + "3FC639FD95800F7495FD0963BB41D9C7F2EAD322478E77033189227A5C6DFAC3"
+ + "729CAA8583261E02B977C1A227BE8C6D5B4C010AC2FE1BF2843AD344FC439017"
+ + "138658B1E169C560750034D8BC30CB29CAE528391F6DDD043EC18D2440407D39"
+ + "80D113491C3E39DFB1AB2082CADF3067E0C5D32A800BAAEC704334BB88225A67"
+ + "2A170CD58583B38A3C8F9F9B6F6B66AFF971A88C0A143A465D31C9162916F996"
+ + "DDA19B54251BECA336ADA65E7AE9D40F8DA49B7F80FE5F1F0138314114FC0493"
+ + "DBDD63EF43546404CD355ECBBBA3F1169C6480BDAEEE8467259A54BE474E7527"
+ + "4A692F63EAAA564BD5F85867A850D7B71ADDF4ED05FA2CECC32235115EA26310"
+ + "4AABBAD196714543EDA4F7A63D1B37C691DB1C24A82E27FFB532EAA8C1F14EC1"
+ + "4D679F0E2AFBB2B730D9F8941E2DB9BB6C0F60E9C98C6CD7CDE94A44C76E9146"
+ + "2BB6067C6A2FFED7A87A84A2ABF3575760E0C2BFB4CDE6D498B326358ABBE0B1"
+ + "235A1924229444CB0C6B817B7A9A0F5546FE03595385B517418E47879FE1CBB1"
+ + "5E29C97D8E30E6E8B8AE6396FF8C7871661B63F6536CD5D7F40B99948A6A7C32"
+ + "9EEB98B2E69CFDE6E7335DDED89B3AE39C4FD8BC58895744F65E2BEA882949D3"
+ + "794D87521F6C1C8CC5951120D2A20CF7FE72DD80193E0A3A595BCDC6D0DE4DF9"
+ + "8AC35146F78E5FEA7E1F2A72B02FA35921954E2EB84DDA9CBC463C932427DC8D"
+ + "026679E40FF747468936BF5D4999B766A08B4E49ACAB01E195C6537AB2051A01"
+ + "DE59F2F4A4000F98FFE3184A2E196DC652F7C1050C74745FF148386AC44A9BD9"
+ + "4BED7E1C2E844373FDA98C49045B7010642FAF1BC94FFF57D8B97725CB113769"
+ + "F546A48C311B521210D666DB143E571EF50D8479C4CCFC6FF2805BB88F4D3C86"
+ + "9DAB564DB3F3BC9EE47ECFEB10F51CEE5B2ED5358B883A2A43F25B2B9037AD02"
+ + "3C5EBB6C79CC4F08F53EA8B82BBA3A85ACA0D2FFDCDD668DE441618A4F60EC04"
+ + "841125F776CBBF995234DE622F23688A70C34735C74A109AEA2F62916CECD535"
+ + "B48A865C91B57D148D0B1FE7DEDECFDC8732BC20908402873EEF3F3AC9150823"
+ + "BB12013D9C8ECFB49B965419710B7F575BC3ECFAB67268F69005DBC4B9658FDD"
+ + "0851359AD79A007F068B47B6AD62AB66DF4DC78D3059E29FACEEA7EB47E076E9"
+ + "7C7E27EFB9D7A8F65357A2E10BF7C27E823AD3071FFF7D5E76D93E784786A0A8"
+ + "11F70ED89072F9E801FF0AC711DF9904820382A8AE96CEDE7099714268A12287"
+ + "91F77F93A97E444A8E96D185CA8B102BA2417FB0E054D3A13CA03093D86B5D7E"
+ + "B699F4126CA53B8DEF2182E3C1E46C9C0D0DF6C6A033C36283BB7F46D18285B7"
+ + "8A12AC1B05DD2398E428188949C5B1994E2A1659184A3CDE28CB39F5021B3A16"
+ + "8AD6E65CC95A042C83F312940BD46FFCA84574AFEFE494F0A1E8E68B8AA5771A"
+ + "AF66DB233A9673430EC5738CAFA2009E2E48FC75F3CDF9D9F9CFBB74C78A61B7"
+ + "709849E16F7162914E76E96A2EDFEEB57671032AF9C19B6DFF2DC80DD12994E1"
+ + "FB5F52ADECC864C13672029AD20FFB8D28C5A3E2A7895A2C0AA74DBBDE9AC8B6"
+ + "50E38F76A3CFA519195F4C8D552ACBA502FF49C0D69A6067E3829488F18B8AE3"
+ + "AE45BD954D667657D70E632923D29FA0CE43A20FECC86CAF72EE201268F259F7"
+ + "3397BAB2A249EFFBBB7CDDD5FB3D0762C18B5DC00AECA5D37E3BD34F28B3C2A3"
+ + "C5EDE116D378DCBC30ADB53DA25FDC0D2D433ACF8A83AB00B114B1E49013A6AA"
+ + "C6F41EFB6C36B5CDCF7C5A877D9E9D6E8C1AFC3C32BB6DBBB603394B20DE41F5"
+ + "D1BA85344C5DEE0304BC8182BE60621C15B44D8F83C8FEBD887499BAAB87E144"
+ + "140558C3B6EB1B2420203C0513496A24AE6C4B3E4A563967D7EC85FFFC6D6825"
+ + "465CE0D89CA4AAECE0E57C8B970C5DB55213E22FB18A3F46EE1556A2F0D3E45A"
+ + "6144A0BDC0434772354C0E0A7586E95F"),
+ };
+
+ @Test
+ public void sigGenKat() {
+ assumeEnabled();
+
+ for (SigKatVector v : SIG_KAT_VECTORS) {
+ if (!isAvailable(v.param)) {
+ continue;
+ }
+ SlhDsa signer = new SlhDsa(v.param);
+ try {
+ signer.importPrivateKey(v.sk);
+ byte[] sig = signer.signDeterministic(v.msg, v.ctx);
+ assertArrayEquals("sigGen KAT, " + v.name,
+ v.expectedSig, sig);
+ }
+ catch (WolfCryptException e) {
+ /* verify-only builds cannot import private keys or sign */
+ skipIfNotCompiledIn(e);
+ }
+ finally {
+ signer.releaseNativeStruct();
+ }
+ }
+ }
+
+ @Test
+ public void sigVerKat() {
+ assumeEnabled();
+
+ for (SigKatVector v : SIG_KAT_VECTORS) {
+ if (!isAvailable(v.param)) {
+ continue;
+ }
+ SlhDsa verifier = new SlhDsa(v.param);
+ try {
+ verifier.importPublicKey(v.pk);
+ assertTrue("sigVer KAT, " + v.name,
+ verifier.verify(v.expectedSig, v.msg, v.ctx));
+
+ /* Tampered signature must fail. */
+ byte[] bad = v.expectedSig.clone();
+ bad[0] ^= 0x01;
+ assertFalse("sigVer KAT tampered, " + v.name,
+ verifier.verify(bad, v.msg, v.ctx));
+ }
+ finally {
+ verifier.releaseNativeStruct();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java
index f5b7b9ae..01fa9f4d 100644
--- a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java
+++ b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java
@@ -57,6 +57,7 @@
MlKemTest.class,
XmssTest.class,
LmsTest.class,
+ SlhDsaTest.class,
WolfObjectTest.class,
WolfSSLCertManagerOCSPTest.class,
WolfSSLX509StoreCtxTest.class,