mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-08-13 02:41:39 +02:00
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). - Preserve the verify return code on a failed SLH-DSA CertificateVerify rather than flattening every non-zero result to SIG_VERIFY_E. wc_SlhDsaKey_Verify already returns SIG_VERIFY_E on a real mismatch, so the failure semantics are unchanged while WC_PENDING_E (async crypto callbacks) and hard errors now propagate, matching ML-DSA and Falcon. Protocol version gating: - SLH-DSA is defined for TLS 1.3 only, so the schemes are no longer offered to a TLS 1.2 peer, and MatchSigAlgo and PickHashSigAlgo pin an SLH-DSA certificate both to the scheme for its exact parameter set and to TLS 1.3. - Reject a Falcon, ML-DSA or SLH-DSA key in the TLS 1.2 CertificateVerify with SIG_TYPE_E. No signature scheme below TLS 1.3 covers a post-quantum key, the record is reserved for a classic signature, and the signing switches have no post-quantum case, so continuing would have sent the reserved buffer's uninitialized tail. 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. - Drop a half-sent streamed CertificateVerify in wolfSSL_clear. Left in place, the resume guard would fire on the next handshake and re-send the previous one's signature into a different transcript. - 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: - Keep MAX_X509_SIZE a fixed 9 KB for post-quantum builds. It sizes a static per-certificate slot embedded by value in every cached session, so it must not scale with a post-quantum signature; nor may it derive from the enabled ML-DSA level, or a level-restricted build would silently drop certificates that a full build keeps. - Add MAX_CERT_WIRE_SZ for the largest certificate that may appear in a handshake message, sized from the enabled post-quantum signatures, and derive MAX_CERTIFICATE_SZ from it instead of from MAX_X509_SIZE. - Add MAX_CERT_MSG_DEPTH for the chain depth assumed when sizing the certificate message. MAX_CHAIN_DEPTH bounds how deep a chain may be verified, while this sizes a buffer an unauthenticated peer can make us allocate, so it is trimmed to 5 when a post-quantum certificate has inflated the per-certificate size. Classic builds are unchanged. - Size the CertificateVerify 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 and its growth is documented in README.md. - Order Scv13Args widest member first so it carries no interior padding and still fits ssl->async->args under WOLFSSL_ASYNC_CRYPT together with WOLFSSL_DUAL_ALG_CERTS. Dual-algorithm certificates: - Reserve the two signature length prefixes in the in-place CertificateVerify sizing that the streamed path already accounted for. - Build the PreTBS for an alternative signature check from the certificate size minus both signatures, and retry once at a size the canonical re-encode cannot exceed when that estimate turns out short. The estimate keeps the allocation small on constrained targets, and wc_GeneratePreTBS reports an encoder failure as WOLFSSL_FAILURE, which is zero, so a non-positive result is now an error instead of silently skipping ConfirmSignature and reading as a verified signature. Device held private keys: - Support an SLH-DSA private key that lives in a device and is referenced by id or label. The parameter set cannot be recovered from a device side identifier, so it is carried from the key type down to wc_SlhDsaKey_Init_id and wc_SlhDsaKey_Init_label, and the key is released with wc_SlhDsaKey_Free once the certificate and key pair is checked. Robustness: - Check the SlhDsaParamToType, wc_SlhDsaKey_PublicSizeFromParam and wc_SlhDsaParamToOid results in the certificate and key load paths. - Zeroize an SLH-DSA key before wc_SlhDsaKey_Init, which can return NOT_COMPILED_IN before it clears the object, in both the certificate load path and AllocKey. - Take the alternative key's parameter set from the certificate's sapkiOID rather than keyOID, which describes the native key. - Re-initialise across hash families in wc_SlhDsaKey_PublicKeyDecode as wc_SlhDsaKey_PrivateKeyDecode already does. The hash objects share a union selected by family, so importing across families writes the new family's state over the old one's and orphans it. - Copy pkCurveOID in SetSSL_CTX when only SLH-DSA is enabled, matching the struct member guard. Without it the field stayed zero and the signature scheme matching above was dead in exactly that build. - Derive the per parameter set WOLFSSL_SLHDSA_PARAM_NO_* macros from the group level exclusions, and select WC_SLHDSA_DEFAULT_PARAM with those same macros, so the parameter table and the TLS mappings cannot disagree. - Add SLH-DSA to the lean build WOLFSSL_MAX_SIGALGO carve-out, since twelve more entries no longer fit the small list. - Prefix the new SLHDSA_ALL_NO_* macros in the installed header with WC_. 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. These sign with the entity key, so they are excluded from verify-only builds. - Interrupt the streamed CertificateVerify with one WANT_WRITE and with several on the same record, and assert the handshake still completes and re-emits identical bytes, which the blocking .conf handshakes never exercise. The record to interrupt is counted first, because the server's record batching differs between builds. Where the flight is flushed as a single write the send is retried below SendTls13CertificateVerify, so these do not by themselves cover the fragOffset resume path. - Drive the streamed path with an ML-DSA leaf under a negotiated max_fragment_length, covering it for a non SLH-DSA algorithm. - Reject a TLS 1.2 handshake that presents an SLH-DSA client certificate. - Map every compiled-in scheme from its wire code point to the key OID, and extend the exhaustive SaToNid coverage with the twelve new algorithms. - Accept an SLH-DSA private key referenced by id and by label. Build configuration: - configure.ac: --enable-slhdsa now keeps the certificate/ASN code enabled (as --enable-mldsa does), so an SLH-DSA-only build with RSA, ECC and DH disabled configures instead of erroring that ASN is off. - Guard the WOLFSSL, WOLFSSL_CTX and WOLFSSL_X509 pkCurveOID members for WOLFSSL_HAVE_SLHDSA, so an SLH-DSA-only build declares the field the handshake and CopyDecodedToX509 already reference under an SLH-DSA guard. - Mark checkKeySz used in the SLH-DSA branch of ProcessBufferCertPublicKey; SLH-DSA is the only certificate signature algorithm with no minimum-size check, so an SLH-DSA-only build otherwise tripped -Wunused-parameter. - Propagate haveSlhDsaSig in wolfSSL_set_SSL_CTX, which copied the Falcon and ML-DSA flags but not the SLH-DSA one. - CI: add a SHA2-only SLH-DSA build (--enable-slhdsa=sha2) so the SHAKE-disabled combined-maxima guards are exercised, and an async crypto build with dual-algorithm certificates, which is the only configuration that compiles the in-place fragmented CertificateVerify send.