Skip to content

Add SLH-DSA entity certificate support for the TLS 1.3 handshake signature#29

Open
Frauschi wants to merge 3 commits into
masterfrom
claude/slh-dsa-tls-handshake-ek53vy
Open

Add SLH-DSA entity certificate support for the TLS 1.3 handshake signature#29
Frauschi wants to merge 3 commits into
masterfrom
claude/slh-dsa-tls-handshake-ek53vy

Conversation

@Frauschi

@Frauschi Frauschi commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Description

Adds support for using SLH-DSA (FIPS 205) entity certificates for the TLS 1.3 handshake signature in the CertificateVerify message. SLH-DSA was already usable for certificate chains (verify + creation); this wires it into the handshake itself so a wolfSSL client or server can authenticate with an SLH-DSA leaf key. TLS 1.3 only, mirroring the existing ML-DSA integration.

Follows draft-reddy-tls-slhdsa: the twelve SignatureScheme code points 0x09110x091C (which match oqs-provider and the FIPS 205 OIDs already present in asn.c), pure SLH-DSA with an empty context.

Fragmented CertificateVerify. SLH-DSA signatures range ~7.8KB–50KB, so a single CertificateVerify can exceed the 16KB TLS record limit. SendTls13CertificateVerify now fragments the message across multiple encrypted records when needed (mirroring SendTls13Certificate); the single-record fast path is unchanged for all other algorithms. Incoming reassembly is already handled generically by DoTls13HandShakeMsg. WC_MAX_CERT_VERIFY_SZ and MAX_X509_SIZE gain SLH-DSA branches.

Summary of changes:

  • wolfssl/internal.h: slhdsa_*_sa_algo enum values, wire code points, SIG_SLHDSA mask, peerSlhDsaKey field, haveSlhDsaSig flags.
  • src/tls13.c: EncodeSigAlg/DecodeTls13SigAlg cases (ML-DSA and SLH-DSA share the 0x09 major byte with disjoint minors), sign via wc_SlhDsaKey_Sign, verify via wc_SlhDsaKey_Verify, decodeSlhDsaKey, and the fragmented CertificateVerify emit.
  • src/internal.c: shared sa_algo↔param↔minor mapping helpers, DecodeSigAlg, AddSuiteHashSigAlgo/InitSuitesHashSigAlgo (advertising only compiled-in parameter sets), ProcessPeerCerts peer-key decode, DecodePrivateKey_ex, alloc/free/reuse key plumbing, haveSig masks.
  • src/ssl_load.c: ProcessBufferTryDecodeSlhDsa plus cert/key-OID have-flag and keyType detection.
  • wolfcrypt/src/wc_slhdsa.c / wolfssl/wolfcrypt/wc_slhdsa.h: fix a pre-existing SHA2-only crash. The buffer-sizing maxima (SLHDSA_MAX_* and WC_SLHDSA_MAX_PRIV/PUB/SEED) were conditioned only on the SHAKE-family disable guards, so a --enable-slhdsa=sha2 build collapsed them to the 128-bit level while the SHA2 tables still used larger n/a/h_m — overflowing the FORS/XMSS nodes stack buffers when signing (stack-smashing abort), and failing ExportPrivate with BAD_LENGTH_E on the 192/256 sets. Combined SLHDSA_ALL_NO_* guards now treat a class as absent only when it is gone from both hash families; SHAKE-only and all-parameter builds are unchanged.

Testing

  • wolfSSL self-interop (new tests/test-tls13-slhdsa-entity.conf, registered in tests/suites.c): a self-signed SLH-DSA-SHAKE-128f entity certificate whose ~17KB signature forces multi-record CertificateVerify send + reassembly. Passes server-auth and mutual-auth through ./tests/unit.test.
  • Regression: standard RSA/ECC and ML-DSA-44 TLS 1.3 handshakes unaffected; clean -Werror builds across SHAKE-only, SHA2-only, combined, and SLH-DSA-only (no ML-DSA/Falcon) configs.
  • SHA2 crash fix: under --enable-slhdsa=sha2, benchmark -slhdsa signs all SHA2 sets, testwolfcrypt SLH-DSA (keygen KATs + sign/verify) passes, and a TLS 1.3 handshake with a SHA2 SLH-DSA cert completes. SHAKE-only/combined builds still pass.
  • Cross-implementation interop with OpenSSL 3.4 + oqs-provider (built from source; harness added as scripts/slhdsa-oqs-interop.sh). All pass in both signing directions:
    • SHAKE-128f (fragmented): wolfSSL signs → OpenSSL+oqs verifies, and OpenSSL+oqs signs → wolfSSL verifies.
    • SHA2-128s: wolfSSL signs → OpenSSL+oqs verifies.
    • OpenSSL+oqs also parses/verifies wolfSSL-generated SLH-DSA X.509 certs.

Note: OpenSSL+oqs advertises a very large signature_algorithms list; the interop harness constrains the peer's -sigalgs to the target scheme so it isn't truncated out of wolfSSL's sig-algs list. A follow-up could raise wolfSSL's received sig-algs list cap.

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

🤖 Generated with Claude Code


Generated by Claude Code

claude added 3 commits July 7, 2026 20:42
…ature

Implement use of SLH-DSA (FIPS 205) entity certificates for TLS 1.3
client/server authentication, i.e. producing and consuming the
CertificateVerify handshake signature with SLH-DSA. Follows
draft-reddy-tls-slhdsa: the twelve SignatureScheme code points 0x0911-0x091C
(matching oqs-provider and the FIPS 205 OIDs already present in asn.c), pure
SLH-DSA with an empty context. TLS 1.3 only, mirroring the existing ML-DSA
integration.

Because SLH-DSA signatures range from ~7.8KB to ~50KB, a single
CertificateVerify can exceed the 16KB TLS record limit. SendTls13CertificateVerify
now fragments the message across multiple encrypted records when needed
(mirroring SendTls13Certificate); the single-record fast path is unchanged for
all other algorithms. Incoming reassembly is already handled generically by
DoTls13HandShakeMsg. WC_MAX_CERT_VERIFY_SZ and MAX_X509_SIZE gain SLH-DSA
branches so the large signatures fit the send buffer and the handshake-message
reassembly cap.

Details:
- internal.h: slhdsa_*_sa_algo enum values, wire code points, SIG_SLHDSA mask,
  peerSlhDsaKey field, haveSlhDsaSig option flags.
- tls13.c: EncodeSigAlg/DecodeTls13SigAlg cases (ML-DSA and SLH-DSA share the
  0x09 major byte with disjoint minors), sign via wc_SlhDsaKey_Sign, verify via
  wc_SlhDsaKey_Verify, decodeSlhDsaKey, fragmented CertificateVerify emit.
- internal.c: shared sa_algo<->param<->minor mapping helpers, DecodeSigAlg,
  AddSuiteHashSigAlgo/InitSuitesHashSigAlgo (advertising only compiled-in
  parameter sets), ProcessPeerCerts peer-key decode, DecodePrivateKey_ex,
  Alloc/Free/Reuse key plumbing, haveSig masks.
- ssl_load.c: ProcessBufferTryDecodeSlhDsa and cert/key-OID have-flag and
  keyType detection.
- wc_slhdsa.h: WC_SLHDSA_DEFAULT_PARAM placeholder macro (the SHAKE family may
  be disabled when only SHA2 is built, so a fixed SHAKE placeholder cannot be
  assumed).

Testing: adds tests/test-tls13-slhdsa-entity.conf (registered in suites.c)
using a self-signed SLH-DSA-SHAKE-128f entity certificate whose ~17KB signature
exercises fragmented CertificateVerify send and reassembly in both directions,
plus certs/slhdsa/gen-slhdsa-entity-cert.c to regenerate the fixtures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeLwE2QcsVHDFR5iXW6Hcj
The SLHDSA_MAX_N/A/H_M/INDICES_SZ/MD buffer-sizing maxima in wc_slhdsa.c and
the WC_SLHDSA_MAX_PRIV_LEN/PUB_LEN/SEED public maxima in wc_slhdsa.h were
conditioned only on the SHAKE-family disable guards
(WOLFSSL_SLHDSA_PARAM_NO_256/192/SMALL/FAST). In a SHA2-only build
(--enable-slhdsa=sha2) the SHAKE family is disabled, so those guards are all
defined and the maxima collapse to their 128-bit-level values, while the SHA2
parameter tables remain fully enabled with larger n/a/h_m. Signing then wrote
the FORS/XMSS 'nodes' stack buffers (sized (SLHDSA_MAX_A+1)*SLHDSA_MAX_N and
(SLHDSA_MAX_H_M+2)*SLHDSA_MAX_N) using the true parameters and overran them,
causing a stack-smashing abort; wc_SlhDsaKey_ExportPrivate similarly failed
with BAD_LENGTH_E on the 192/256 SHA2 sets.

Derive combined SLHDSA_ALL_NO_* guards (in wc_slhdsa.h) that treat a
size/speed class as absent only when it is absent in BOTH the SHAKE and SHA2
families, and size every maximum off those. SHAKE-only and all-parameter
builds are unaffected: with the SHA2 family off, WOLFSSL_SLHDSA_NO_SHA2 makes
each combined guard equal to its SHAKE guard; with all sets enabled none of
the guards are defined, so the maxima resolve to the same largest values as
before.

Verified under --enable-slhdsa=sha2: benchmark signs all SHA2 parameter sets,
testwolfcrypt SLH-DSA (incl. keygen KATs and sign/verify) passes, and a TLS
1.3 handshake using a SHA2 SLH-DSA certificate completes. SHAKE-only and
combined builds still pass with no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeLwE2QcsVHDFR5iXW6Hcj
Adds scripts/slhdsa-oqs-interop.sh, a manual interoperability harness that
exercises the SLH-DSA CertificateVerify handshake signature between wolfSSL and
OpenSSL 3.4 + oqs-provider (which supplies the draft-reddy-tls-slhdsa TLS
signature schemes at code points 0x0911-0x091C that wolfSSL matches). It runs
three scenarios:

  A) wolfSSL server signs  -> OpenSSL+oqs client verifies  (SHAKE-128f, SHA2-128s)
  B) OpenSSL+oqs server signs -> wolfSSL client verifies   (SHAKE-128f)

SHAKE-128f's ~17KB signature makes the CertificateVerify span multiple TLS
records, so the harness also cross-checks fragmented CertificateVerify send and
reassembly between the two stacks. The script header documents the from-source
build of liboqs (main, OQS_USE_OPENSSL=OFF), OpenSSL 3.4, and oqs-provider
(main); OpenSSL 3.4 is used because provider TLS signature algorithms work
there and it has no native SLH-DSA to shadow oqs-provider's (OpenSSL >= 3.5
does not expose SLH-DSA as a TLS sig alg and causes oqs-provider to drop it).

Verified locally: all three scenarios pass in both signing directions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeLwE2QcsVHDFR5iXW6Hcj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants