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,