Allow a quantum-safe-only TLS 1.3 stack - ML-KEM key exchange with ML-DSA or
SLH-DSA authentication - with RSA, ECC and DH all disabled (the CNSA 2.0 /
EO 14412 posture).
- Treat a usable standalone ML-KEM group as a valid key exchange and
Falcon/ML-DSA/SLH-DSA as valid authentication, so the "no cipher suites"
#errors no longer fire (ssl.c, internal.h), and extend the CopyDecodedToX509
pkCurveOID guard to match the struct member guards (internal.c).
- asn.c: guard the RSA/ECC-only certificate signing helpers so cert generation
compiles with RSA and ECC disabled, while keeping MakeSignatureCb available
for the WOLFSSL_CERT_SIGN_CB (TPM/HSM) offload path.
- tls.c: advertise the psk_dhe_ke exchange mode when a standalone ML-KEM group
is usable on the client side, that is when the build has both key generation
and decapsulation. Without it an ML-KEM-only build fell back to psk_ke, the
resumption key schedules diverged, and the first AEAD record failed to
authenticate (AES_GCM_AUTH_E), breaking session-ticket resumption.
- tls13.c: hoist the hashAlgo void cast in EncodeSigAlg out of the Ed25519 and
Ed448 cases so a build with no classic signature algorithm still marks the
parameter used.
- Examples and tests: add ML-DSA and SLH-DSA certificate selection (gated on
NO_RSA so it only applies in true PQC-only builds) and default to TLS 1.3
when no classic auth is built in and a post-quantum signature is available
(test.h, client/server/echo*, test_tls13.c). SLH-DSA uses the SHAKE-128s leaf
certificates and their shared 128s root.
- CI: add PQC-only entries to the pq-all.json config list (ML-DSA and SLH-DSA),
each exercising the build, a full TLS 1.3 handshake, and ML-KEM-only
session-ticket resumption.
Whether a build can authenticate with a post-quantum certificate is three
separate questions - is the algorithm compiled in, are test credentials wired
up for the enabled parameter set, and can this build sign - so test.h answers
them with three tiers of macro rather than testing WOLFSSL_HAVE_MLDSA and
WOLFSSL_HAVE_SLHDSA directly:
- TEST_HAVE_MLDSA_CERT_FILES / TEST_HAVE_SLHDSA_CERT_FILES: the credential
paths name real files. ML-DSA needs an enabled parameter level; SLH-DSA needs
a 128s parameter set, since both families share a 128s root and only those
certificates are wired up, and PEM decoding, since the entity certificates
ship as PEM only. Loading a certificate needs verification support alone, so
test_tls13.c uses this tier - it only needs a certificate on the context.
- TEST_HAVE_MLDSA_CERTS / TEST_HAVE_SLHDSA_CERTS: the above, plus the ability
to sign. A verify-only build derives WOLFSSL_MLDSA_NO_SIGN and drops private
key support entirely, so it can neither load the key nor produce a
CertificateVerify. The example credential ladders use this tier.
- TEST_HAVE_PQC_CERT_AUTH: either of those, plus WOLFSSL_TLS13. Both algorithms
are TLS 1.3 only, so folding the version into the macro keeps the version
defaults and the PSK fallbacks in the client, server and echo examples from
disagreeing. Falcon is deliberately absent, having no credentials in the
ladders, so a Falcon-only build keeps the historic TLS 1.2 PSK behavior.
A build that fails a gate now falls back to PSK instead of naming credentials
it cannot use, which removes two hard #errors that were reachable from
configure: --enable-slhdsa with only non-128s parameter sets broke every build
that includes test.h, even ones with RSA and ECC enabled that would never touch
an SLH-DSA credential.
Also in the credential material and the configurations this opens up:
- test.h: pair the DER ML-DSA certificate with mldsa<N>-key.der. The
mldsa<N>_priv-only.der files are standalone PKCS#8 encoding vectors from an
unrelated key pair, so a build without PEM decoding presented a certificate
and signed with a key that did not match it, and the peer rejected the
CertificateVerify.
- echoserver.c: load the SLH-DSA key with CERT_FILETYPE like every neighboring
branch, rather than hardcoding WOLFSSL_FILETYPE_PEM against a certificate
loaded in the build-dependent format.
- internal.c, tls13.c, ssl_load.c: mark ssl, sigOut, keyType and keySz used.
Every reader of these sits in a per-algorithm block, so a build with no
signing algorithm at all left them unused and -Werror rejected the library.
- tests/api.c: skip test_wolfSSL_PKCS8 unless RSA, ECC or PEM decoding is
available. With none of them every key-loading body compiles out, leaving the
file locals unused and nothing for the test to do.
Added documentation of the defines for algorithms and assembly code and math.
user_settings_embedded.h has configuration defines at the top that are set to
0 for off and 1 for on. Setting these turns on the appropriate defines.
A crypto callback that returns WC_PENDING_E for a TLS record cipher
silently corrupted records: Encrypt()/Decrypt() advance the cipher
state to CIPHER_STATE_END before the pending check, so on resume the
record is shipped without re-running the cipher.
WOLF_CRYPTO_CB_ASYNC_POLL gives crypto callback devices the QAT/Nitrox
"poll to fill output" completion model. On WC_PENDING_E the async event
stays queued; wolfSSL_AsyncPoll() re-enters the device with the new
WC_ALGO_TYPE_ASYNC_POLL (wc_CryptoCb_Poll) to finish the job and fill
the output buffer. The re-entry only polls while the event is still
pending, and a device that cannot complete the job (no poll support, or
it reports nothing pending) hard-fails with WC_HW_E rather than
reporting the op done with an unfilled buffer. Only the two async record
ciphers (AES and 3DES markers) are routed to poll completion, and only
when crypto callbacks are the async backend (not QAT/Cavium/SW);
handshake PK keeps the re-invoke model. The wolfSSL_AsyncPop eviction is
gated for poll-capable devices so the existing resume-at-CIPHER_STATE_END
path becomes correct with no record state-machine changes.
Without the feature (and without a software/QAT/Cavium backend) a
pending bulk cipher op now errors out with ASYNC_OP_E instead of
corrupting the record, and configure/cryptocb.c warn about the
unsupported combination.
Tests in tests/api/test_async.c cover direct AES-GCM/CBC/CCM and 3DES
poll completion in both directions at multiple pend depths, negative
cases for cipher types defined but not dispatched (ChaCha, single DES),
and full TLS 1.3 handshake+echo: an encrypt-offload run, a both-
directions run that offloads encrypt and decrypt on both peers using a
per-peer device, and the no-poll failure path.
Add WOLFSSL_TLS_READ_AHEAD (--enable-readahead), toggled at runtime via
wolfSSL_set_read_ahead(). When enabled, the record-header read pulls a
full record in one recv() so the body arrives without a second syscall.
The receive window is configurable with
wolfSSL_CTX/SSL_set_default_read_buffer_len() (OpenSSL-compatible):
0 keeps the one-record default, a larger value coalesces several records
per recv(), a smaller value caps the per-connection buffer footprint.
Records exceeding the window are still received correctly, the buffer
grows on demand and is reallocated back down to the window afterwards so
the retained footprint stays bounded.
Includes docs, API tests, and a benchmark toggle.
Make every --enable-tinytls13 spelling build and pass locally, and grow the
CI matrix to cover them. These are fixes found while testing the configs the
CI workflow had not actually exercised.
- internal.h, internal.c, ssl_load.c: include ML-DSA and Falcon in the
pkCurveOID member and producer guards so the PSK plus ML-DSA build compiles.
- tls13.c: gate the DoTls13CertificateVerify definition on NO_CERTS to match
its call site.
- settings.h: let the AES-256 adder survive the floor, default the
user_settings path to the SHA-256 floor, make WOLFSSL_NO_MALLOC opt-in so
the test suite still runs, and keep ML-DSA ASN.1 for the cert profile.
- configure.ac: drive ENABLED_ASM and emit WOLFSSL_NO_ASM for the small C
floor, restrict SP math to P-256, strip ML-DSA ASN.1 only on the PSK floor,
and print a notice for the reduced security cert verify.
- examples: guard the cert loading paths for NO_CERTS and treat NO_CERTS as
PSK mode in echoserver and echoclient.
- Add examples/configs/tinytls13_smoke.c, an in memory TLS 1.3 handshake test
that drives PSK, ECDSA, ML-DSA-65 and RSA-PSS chain verify, plus forced
cipher suites, for builds with no example or unit test harness.
- certs: add ECDSA leaves signed by the ML-DSA-65 and RSA-PSS CAs so the cert
profiles drive a real PQC and PSS chain verify in CI.
- .github/workflows/tinytls13.yml: cover every profile and adder, run the
smoke handshake on the build verified configs, and least privilege the
workflow token.
- Retry wolfSSL_write on the same recoverable send errors the plain-UDP
baseline already retries on: EAGAIN/EWOULDBLOCK surface as WANT_WRITE
and ENOBUFS as SOCKET_ERROR_E with errno preserved. The buffered
record is flushed by the retried call without re-encrypting.
- Treat an explicit -? as a help request: print usage to stdout and
exit 0, keeping stderr and a failure exit for genuine option errors.
- Enumerate ciphers with wolfSSL_get_cipher_list() instead of an 8 KiB
stack buffer, and range-check -p and -b like the other numeric
options.
- Document in BuildMessage that the FIPS<2 path overwrites the
explicit-nonce placeholder inside BuildMessage itself, and that the
one path transmitting the bytes as written (ATOMIC_USER MacEncryptCb)
still emits the sequence number that RFC 5288 et al. prescribe.
Fail loudly if clock_gettime() ever fails instead of computing
throughput from uninitialized stack, and warn when -z is combined with
-s since the sink-send only applies to the client.
dtls_bench.c is built whenever DTLS and the example servers are enabled,
including the cross-mingw-all-crypto multi-test scenario, which cross-
compiles for Windows. It directly includes POSIX-only headers
(<sys/socket.h>, <arpa/inet.h>, <netdb.h>, <net/if.h>) that mingw does
not ship, so the build failed there.
Gate the networking includes and the whole benchmark body behind a
DTLS_BENCH_ENABLED check (WOLFSSL_DTLS, not USE_WINDOWS_API, not
WOLFSSL_NO_SOCK). When the platform lacks POSIX BSD sockets, compile a
small stub main() that reports the tool is unsupported, so the source
tree still builds.
Add examples/benchmark/dtls_bench, a DTLS throughput benchmark that
completes a handshake and then measures bulk-send throughput. It
supports DTLS 1.2 and 1.3, selectable cipher suites, an end-to-end
mode, and a -z sink mode that discards records on the server after the
handshake to isolate the sender's record-layer cost. The socket is set
up with wolfSSL_set_dtls_fd_connected.
Optimize the send path exercised by the benchmark:
- wolfio (EmbedSendTo): cache the per-descriptor socket-type probe
(getsockopt SO_TYPE) in WOLFSSL_DTLS_CTX instead of running it on
every send, removing a syscall from the record send path. The cache
is invalidated whenever rfd/wfd is reassigned.
- internal (BuildMessage): for AEAD suites whose explicit nonce is the
8-byte record sequence number, write the sequence number directly as
nonce_explicit instead of drawing it from the RNG. This covers
AES-GCM (RFC 5288 sec 3), AES-CCM (RFC 6655 sec 3), SM4-GCM/CCM
(RFC 8998 sec 3), and Camellia-/ARIA-GCM which inherit the RFC 5288
construction; ChaCha20 uses an implicit nonce and is excluded. A new
read-only PeekSEQ() helper reads the sequence number without advancing
the per-direction counter, leaving the single mandated increment to
writeAeadAuthData().
Also ignore the built dtls_bench binary in .gitignore.
Extend the swdev callback to handle ECC operations: keygen, ECDH, sign,
verify, get-size, get-sig-size.
Relax WOLF_CRYPTO_CB_ONLY_ECC guards in the test suite so that tests run
under swdev, and wire wc_SwDev_Init/Cleanup into testsuite, client, and
server. Two tests are intentionally kept excluded even with swdev because
they call raw ECC math primitives (wc_ecc_mulmod, on-curve validation in
wc_ecc_import_x963) that are stripped below the cryptocb dispatch layer:
- ecc_mulmod_test (wolfcrypt/test/test.c)
- test_wc_ecc_import_x963_off_curve (tests/api/test_ecc.c)
- x509_str: require CA:TRUE unconditionally in wolfSSL_X509_verify_cert;
verify leaf signature even when verify_cb overrides INVALID_CA
- x509_str: align WOLFSSL_X509_V_ERR_INVALID_CA with OpenSSL value (79)
so OPENSSL_COEXIST builds compile; bump WC_OSSL_V509_V_ERR_MAX to 80
and extend error_test() missing-value table for the new gaps
- asn: reject embedded NUL in dNSName / rfc822Name / URI SAN entries
- internal: re-verify restored ticket peer cert against trust store with
CRL/OCSP checks; clear stale state from session cache on verification
failure
- ticket: bind SNI and ALPN into session ticket via compile-time selected
hash (TICKET_BINDING_HASH_TYPE); reject resumption on mismatch in both
TLS 1.3 and TLS 1.2 paths
- ticket: defer SNI/ALPN binding check until after extensions are parsed
by consolidating into VerifyTicketBinding(), called once after
ALPN_Select in DoTls13ClientHello and DoClientHello; the early
per-call sites ran before extensions were parsed and rejected valid
resumptions in nginx, haproxy, grpc, and CPython integration tests
- ssl_sess: free previous session in wolfSSL_d2i_SSL_SESSION before
overwrite
- examples/client: increase SESSION_TICKET_LEN fallback from 256 to 2048
to support larger tickets
- tests: update SAN NUL fixtures and add parse-time rejection coverage;
add test_tls13_ticket_peer_cert_reverify for CA-removal scenario; skip
it under WOLFSSL_NO_DEF_TICKET_ENC_CB
Windows test code pre-picked a random port via GetRandomPort() (returning
a value in [49152, 65535]) before calling bind(), with no check that the
port was free and no retry on collision. Under load this occasionally
collided with an already-bound port and aborted the test with
"tcp bind failed", producing intermittent Jenkins failures (e.g. PRB
windows-test-v2 #17140 in the OCSP responder test).
The Unix path already does the right thing: bind to port 0 (OS-assigned
ephemeral) and read the port back via getsockname(). The same primitives
exist in Winsock 1.1, so drop the USE_WINDOWS_API guard around the
getsockname block in tcp_listen()/udp_accept() and remove the per-caller
GetRandomPort() workarounds in the OCSP responder, server example, and
the api.c / test_ossl_bio.c test sites. socklen_t is already typedef'd
as int on Windows in test.h.
GetRandomPort() itself is left in place since it is a static inline in a
shipped public test header.