Add SLH-DSA support for the TLS 1.3 and DTLS 1.3 handshake#10901
Add SLH-DSA support for the TLS 1.3 and DTLS 1.3 handshake#10901Frauschi wants to merge 1 commit into
Conversation
|
cf4f9cd to
417c06f
Compare
|
Jenkins retest this please |
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10901
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
417c06f to
e7100ab
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10901
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 3
2 finding(s) posted as inline comments (see file-level comments below)
Info (1)
SHAKE-variant SLH-DSA cases in EncodeSigAlg lack per-parameter-set guards
Function: EncodeSigAlg
Category: Code Consistency / Defensive Programming
The SHA2-family SLH-DSA cases in EncodeSigAlg are individually gated (e.g. #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_128S) whereas the SHAKE-family cases fall through under the single #ifdef WOLFSSL_HAVE_SLHDSA umbrella. This is an internal inconsistency: if WOLFSSL_HAVE_SLHDSA is defined but a specific SHAKE parameter set (e.g. WOLFSSL_SLHDSA_PARAM_128F) is not, the corresponding case statement is still compiled in even though it can never be reached. AddSuiteHashSigAlgo, which feeds the negotiated suite, is correctly per-variant gated, so the enum value would never appear in args->sigAlgo. Because AddSuiteHashSigAlgo, SlhDsaSigMinorToType, and ProcessBufferTryDecodeSlhDsa all enforce per-parameter-set guards at the point where an algorithm is admitted into the session, a SHAKE variant that is not compiled in cannot appear in a live handshake.
Recommendation: For consistency with the SHA2 branch and to make dead-code elimination explicit, add per-parameter-set preprocessor guards around each SHAKE case in EncodeSigAlg. No functional or security change results; the improvement is defensive consistency.
This review was generated automatically by Fenrir. Findings are non-blocking.
e7100ab to
ac897d5
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10901
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
ac897d5 to
f759960
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10901
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
Implement SLH-DSA (SPHINCS+, FIPS 205) as an entity authentication algorithm for the TLS 1.3 and DTLS 1.3 handshake, following draft-reddy-tls-slhdsa. All twelve parameter sets (SHAKE and SHA2 families, 128/192/256 in the f and s variants) are wired into the handshake for signing and verifying the CertificateVerify message; test certificates and configs cover the 128f and 128s sets. Handshake integration: - Map the SLH-DSA signature schemes to and from the wire in the signature_algorithms extension and CertificateVerify. The mapping, advertisement, and OID handling are gated per parameter set so a build only offers, accepts, and maps the variants actually compiled in (including partial SHA2 builds). - Sign and verify the CertificateVerify with an SLH-DSA entity key, and load SLH-DSA private keys and certificates (ssl_load.c, ssl.c, ssl_api_pk.c, asn.c). Streamed CertificateVerify send: - SLH-DSA signatures are large (up to ~50 KB). When the CertificateVerify body exceeds a single record, generate the signature into a connection-level buffer and emit it one record at a time so the output buffer never has to hold the whole signature. This keeps peak memory near one signature plus a single fragment and resumes correctly across a non-blocking WANT_WRITE without recomputing the randomized signature. Gated by WOLFSSL_TLS13_STREAM_CERT_VERIFY (TLS 1.3, non-async, PQC signatures); DTLS and WOLFSSL_ASYNC_CRYPT keep the existing in-place fragmented path. - Dual-algorithm (WOLFSSL_DUAL_ALG_CERTS, BOTH) CertificateVerify bodies are streamed as well. The combined two-signature body may include a variable-length signature, so the body buffer is sized from the per-signature upper bounds and the exact length is recorded after signing; the small trailing slack is never sent. Buffer sizing: - Make MAX_X509_SIZE parameter-aware for post-quantum certificates, sizing it from the largest enabled SLH-DSA or ML-DSA signature rather than a flat 8 KB that cannot hold an SLH-DSA-signed certificate. - Size the CertificateVerify and PreTBS buffers from the actual signature length instead of the worst-case WC_MAX_CERT_VERIFY_SZ, which balloons with SLH-DSA. WC_MAX_CERT_VERIFY_SZ is retained for API compatibility. Tests and certificates: - Add SLH-DSA entity (client and server) certificates for the SHAKE and SHA2 128f and 128s parameter sets, and update the generation script. - Add TLS 1.3 and DTLS 1.3 entity-cert CertificateVerify test configs covering the fragmented (128f) and single-record (128s) send paths for both hash families, wired into suites.c.
f759960 to
9e96534
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10901
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
Summary
Adds SLH-DSA (SPHINCS+, FIPS 205) as an entity authentication algorithm for the TLS 1.3 and DTLS 1.3 handshake, following
draft-reddy-tls-slhdsa. An SLH-DSA leaf key can now be used to sign (and a peer's SLH-DSA leaf to verify) theCertificateVerifymessage, for both server and mutual authentication.All twelve parameter sets are wired into the handshake — the SHAKE and SHA2 hash families across 128/192/256 in both the f (fast) and s (small) variants — with per-parameter-set gating so a build only offers, accepts, and maps the variants actually compiled in. Test certificates and configs cover the 128f and 128s sets for both hash families.
Motivation
SLH-DSA is a stateless hash-based signature scheme standardized in FIPS 205. Its appeal is a conservative security foundation (hash functions only), but its signatures are large — from ~7.8 KB (128s) up to ~49 KB (256f) — which stresses several fixed-size buffers in the TLS/cert code that were sized for classical signatures. This PR adds the protocol support and the buffer-sizing work needed to make it viable, including on memory-constrained targets.
What's included
Handshake integration
signature_algorithmsextension andCertificateVerify.CertificateVerifywith an SLH-DSA entity key.ssl_load.c,ssl.c,ssl_api_pk.c,asn.c,wc_slhdsa.h).--enable-slhdsa=sha2-128s) never advertise or map a variant they cannot perform.Streamed CertificateVerify send (large signatures)
When the
CertificateVerifybody exceeds a single record, the signature is generated once into a connection-level buffer and emitted one record at a time, so the output buffer never has to hold the whole signature.WANT_WRITEwithout recomputing the randomized signature (the already-sent records are bound into the transcript, so the resumed send must emit the identical bytes — this is a correctness requirement, not an optimization).WOLFSSL_TLS13_STREAM_CERT_VERIFY(TLS 1.3, non-async, PQC signatures). It is algorithm-neutral — it also applies to ML-DSA (or any scheme) whenever the body exceeds the negotiatedmax_fragment_length.WOLFSSL_ASYNC_CRYPTbuilds keep the existing in-place fragmented path (see Scope / carve-outs).WOLFSSL_DUAL_ALG_CERTS, BOTH) bodies stream as well: the combined two-signature body is sized from per-signature upper bounds and the exact length is recorded after signing (trailing slack is never sent).Peak send-side memory is ≈ one signature + one record (the record is at most 16 KB, or the negotiated
max_fragment_lengthif smaller), versus the ~2× a naive in-place send holds (the assembled body plus a copy). The reduction is largest exactly where it matters for constrained devices — a smallmax_fragment_length, where the second term is sub-KB and the peak is essentially the signature itself. It also stops the output buffer from staying inflated to the whole signature for the connection's lifetime.Buffer sizing
MAX_X509_SIZEis now parameter-aware for post-quantum certificates, sized from the largest enabled SLH-DSA/ML-DSA signature. The previous flat 8 KB could not hold an SLH-DSA-signed certificate (~8.5 KB), which silently broke the static session-certificate cache paths.CertificateVerifyand PreTBS buffers are sized from the actual signature length instead of the worst-caseWC_MAX_CERT_VERIFY_SZ(which balloons to ~50 KB with SLH-DSA).WC_MAX_CERT_VERIFY_SZis retained for API/backward compatibility and documented as no longer used internally.Tests and certificates
CertificateVerifytest configs covering the fragmented (128f) and single-record (128s) send paths for both hash families, wired intosuites.c(server-auth + mutual-auth scenarios).Enabling
TLS 1.3 is required (enabled by default). The SHA2 family additionally requires SHA-256, SHA-512, and HMAC.
Scope / carve-outs
Dtls13HandshakeSend, and its reliability model must retain the full message for retransmission until ACKed — so streaming yields no memory benefit there. A code comment inDtls13SendFragmentedrecords the follow-up (sign directly into the fragments buffer to avoid the transient second copy; the retransmit copy is a hard floor).WOLFSSL_ASYNC_CRYPTbuilds keep the existing in-place fragmented path. Async introduces a record-AEADWC_PENDING_Eresume point whose cursor-timing requirement conflicts with the non-blockingWANT_WRITEcursor; the in-place path already handles both correctly. Async targets (HW-offload servers) are not memory-constrained, so there is no beneficiary for the added complexity.Testing
./tests/unit.testsuite: pass (normal handshakes + all SLH-DSA configs + DTLS + crypto API tests).-Werror: clean; verified the streaming feature-macrooff path (classical/async builds) also compiles clean.
Known limitations / follow-ups
WOLFSSL_SLHDSA_PARAM_*macros) would strengthen the streaming/fragmentation coverage.WOLFSSL_DUAL_ALG_CERTS, BOTH) path with a fragment-sized signature is not covered by a test config (no dual-alg SLH-DSA test certificates exist); it is correct by construction but unexercised.