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.
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.
RFC 8446 section 8 requires any server instance to accept 0-RTT for a
given ClientHello at most once. Prior to this change wolfSSL's behaviour
diverged from that requirement in several ways:
* ctx->maxEarlyDataSz defaulted to MAX_EARLY_DATA_SZ whenever the
library was built with WOLFSSL_EARLY_DATA, so servers auto-
advertised 0-RTT in NewSessionTicket without the application
asking. RFC 8446 E.5 says 0-RTT MUST NOT be enabled unless
specifically requested.
* The post-accept eviction is compiled out under NO_SESSION_CACHE,
so builds without the cache accepted 0-RTT with no replay defence.
* Stateless self-encrypted tickets do not carry a session ID on the
stateless DoClientTicket decrypt path, so wolfSSL_SSL_CTX_remove_
session could not locate them to evict.
* wolfSSL_SSL_CTX_remove_session always returned 0 on success
regardless of whether the session was actually in the cache,
diverging from OpenSSL's SSL_CTX_remove_session (1 on success,
0 on not-found).
Changes:
* src/internal.c: ctx->maxEarlyDataSz defaults to 0; applications
must opt in with wolfSSL_CTX_set_max_early_data.
* src/tls13.c: #error when WOLFSSL_EARLY_DATA is built with
HAVE_SESSION_TICKET and NO_SESSION_CACHE. Escape hatch
WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY for deployments that take
application-layer responsibility.
* wolfssl/internal.h: imply WOLFSSL_TICKET_HAVE_ID from
WOLFSSL_EARLY_DATA so stateless-ticket issuance populates the
cache under an ID that eviction can find.
* src/ssl_sess.c: wolfSSL_SSL_CTX_remove_session returns 1 when the
session was found (internal-cache hit, or ctx->rem_sess_cb fired
for an external cache), 0 otherwise. Matches OpenSSL semantics.
* src/tls13.c: the 0-RTT acceptance condition in CheckPreSharedKeys
now calls wolfSSL_SSL_CTX_remove_session and checks its return:
the eviction is the check. If the session was in the cache, 0-RTT
is accepted and the single-use requirement is satisfied. If not,
the early_data extension is rejected through the normal path so
the record layer correctly skips in-flight 0-RTT records.
WOLFSSL_MSG at each rejection site.
* doc/dox_comments/header_files/ssl.h: document runtime opt-in.
* tests: four new tests —
test_tls13_0rtt_default_off (fails without default-to-0 fix),
test_tls13_0rtt_stateless_replay (fails without TICKET_HAVE_ID
implication and remove_session gate),
test_tls13_remove_session_return (fails without return-value fix),
test_tls13_0rtt_ext_cache_eviction (fails without ext-cache
counts-as-found fix).
test_tls13_early_data explicitly opts in via
wolfSSL_CTX_set_max_early_data.
tests/api.c: two SSL_CTX_remove_session == 0 assertions updated
to == 1.
scripts/pem.test:
* add setup for WOLFSSL_NO_DER_TO_PEM,
* exit early with skip code if executable dependencies are missing or WOLFSSL_NO_PEM or NO_CODING, and
* add clean skip clauses to convert_to_pem(), compare_pem(), and pem_der_exp(), if WOLFSSL_NO_DER_TO_PEM.