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.
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.
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.
9e96534 to
9991b12
Compare
|
Jenkins retest this please |
There was a problem hiding this comment.
Pull request overview
This PR adds SLH-DSA (SPHINCS+, FIPS 205) as a TLS 1.3 / DTLS 1.3 CertificateVerify authentication algorithm, and updates handshake and certificate buffering logic to support very large post-quantum signatures without relying on oversized fixed buffers.
Changes:
- Adds SLH-DSA signature-scheme mapping, key loading/decoding, and sign/verify integration for TLS 1.3 and DTLS 1.3 handshakes.
- Introduces a TLS 1.3 streamed
CertificateVerifysend path to avoid requiring the output buffer to hold an entire large signature at once. - Updates certificate / signature buffer sizing (e.g.,
MAX_X509_SIZE,WC_MAX_CERT_VERIFY_SZ) and adds new test configs + test cert material for SLH-DSA entity authentication.
Reviewed changes
Copilot reviewed 38 out of 46 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| wolfssl/wolfcrypt/wc_slhdsa.h | Adds combined “family-absence” guards and a default parameter macro to keep size maxima correct across SHAKE/SHA2 builds. |
| wolfssl/wolfcrypt/asn.h | Updates WC_MAX_CERT_VERIFY_SZ definition and documents its API/back-compat intent under SLH-DSA. |
| wolfssl/internal.h | Adds SLH-DSA signature-scheme code points, adjusts MAX_X509_SIZE, and introduces the TLS 1.3 streamed CertificateVerify feature macro + buffer. |
| wolfcrypt/src/wc_slhdsa.c | Updates internal SLH-DSA buffer maxima selection to use combined guards across both hash families. |
| wolfcrypt/src/port/Renesas/renesas_tsip_util.c | Avoids sizing TSIP classic-hardware CertificateVerify buffers using the SLH-DSA-inflated worst-case macro. |
| wolfcrypt/src/asn.c | Uses a computed PreTBS bound for dual-alg alternative signature verification and allocates accordingly. |
| tests/test-tls13-slhdsa-entity.conf | Adds TLS 1.3 entity-certificate tests for SLH-DSA (fragmented path). |
| tests/test-tls13-slhdsa-entity-sha2.conf | Adds TLS 1.3 SHA2-family entity-certificate tests (fragmented path). |
| tests/test-tls13-slhdsa-entity-sha2-128s.conf | Adds TLS 1.3 SHA2-family entity-certificate tests (single-record path). |
| tests/test-tls13-slhdsa-entity-128s.conf | Adds TLS 1.3 SHAKE-family entity-certificate tests (single-record path). |
| tests/test-dtls13-slhdsa-entity.conf | Adds DTLS 1.3 entity-certificate tests for SLH-DSA (fragmented path). |
| tests/test-dtls13-slhdsa-entity-128s.conf | Adds DTLS 1.3 entity-certificate tests for SLH-DSA (single-record path). |
| tests/suites.c | Wires the new test configs into the suite runner behind appropriate compile-time guards. |
| tests/include.am | Adds the new SLH-DSA entity test config files to the dist list. |
| src/tls13.c | Implements SLH-DSA signature-scheme encode/decode, SLH-DSA key decode + verify, and streamed TLS 1.3 CertificateVerify send support. |
| src/ssl.c | Maps new SLH-DSA internal signature-algorithm IDs to OID/NID identifiers. |
| src/ssl_load.c | Adds SLH-DSA private-key “try decode” support in the key-loading probe chain. |
| src/ssl_api_pk.c | Adds SLH-DSA signature algorithm -> NID mapping support. |
| src/internal.c | Advertises SLH-DSA schemes (gated per parameter set), decodes SLH-DSA TLS schemes, and tracks “have SLH-DSA signature” capability. |
| src/dtls13.c | Documents DTLS fragmentation’s copy/peak-memory behavior and notes a future optimization for PQC signatures. |
| certs/slhdsa/server-slhdsa-shake-128s-priv.pem | Adds SLH-DSA SHAKE-128s server private key for tests. |
| certs/slhdsa/server-slhdsa-shake-128f-priv.pem | Adds SLH-DSA SHAKE-128f server private key for tests. |
| certs/slhdsa/server-slhdsa-sha2-128s-priv.pem | Adds SLH-DSA SHA2-128s server private key for tests. |
| certs/slhdsa/server-slhdsa-sha2-128f-priv.pem | Adds SLH-DSA SHA2-128f server private key for tests. |
| certs/slhdsa/client-slhdsa-shake-128s-priv.pem | Adds SLH-DSA SHAKE-128s client private key for tests. |
| certs/slhdsa/client-slhdsa-shake-128f-priv.pem | Adds SLH-DSA SHAKE-128f client private key for tests. |
| certs/slhdsa/client-slhdsa-sha2-128s-priv.pem | Adds SLH-DSA SHA2-128s client private key for tests. |
| certs/slhdsa/client-slhdsa-sha2-128f-priv.pem | Adds SLH-DSA SHA2-128f client private key for tests. |
| certs/slhdsa/include.am | Adds SLH-DSA entity cert/key artifacts to distribution list. |
| certs/slhdsa/gen-slhdsa-mldsa-certs.sh | Extends the cert generation script to produce SLH-DSA entity leaves for handshake tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| args->sendSz = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ + | ||
| HASH_SIG_SIZE + VERIFY_HEADER + (int)args->sigLen; | ||
| #ifdef WOLFSSL_DUAL_ALG_CERTS | ||
| args->sendSz += (int)args->altSigLen; | ||
| #endif |
| #ifdef WOLFSSL_HAVE_SLHDSA | ||
| /* ssl->peerCert->sapkiDer is the alternative public key. Hopefully it is an | ||
| * SLH-DSA public key. Convert it into a usable public key. */ |
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.