Merging TranslateReturnCode into wolfSSL_LastError dropped the
IP_SOCK_getsockopt(SO_ERROR) lookup emNET integrations need to retrieve
the canonical IP_ERR_* for a failed recv/send, leaving a broken branch
that returned the raw value and mishandled the POSIX-facade convention.
Restore the historic lookup (fixing the optlen pointer-vs-int typo
along the way) and add a CI test that builds wolfSSL with
-DWOLFSSL_EMNET against a clean-room shim providing an emNET-faithful
IP_SOCK_getsockopt (SO_ERROR-then-errno fallback, since Linux does not
stash EAGAIN in SO_ERROR); recv/send fall through to glibc.
The emNET `wolfSSL_LastError` branches were incorrect. The second one
was never hit and would never compile. The first one inverts error codes
that should not be inverted.
This fixes that code and adds a test with a shim layer to test emNET
calls without using emNET.
Replace the liboqs-based pre-standardization SPHINCS+ implementation
with the native FIPS 205 SLH-DSA implementation across the
certificate / ASN.1 / X.509 layers, and add SLH-DSA-rooted test
certificates plus TLS 1.3 .conf scenarios that exercise the new
verification path. All liboqs SPHINCS+ code is removed.
This enables SLH-DSA for certificate chain authentication: CA
certificates signed with SLH-DSA, certificate signature verification
against an SLH-DSA root. TLS 1.3 entity authentication via
CertificateVerify with SLH-DSA will be added in a follow-up PR.
Follows RFC 9909 (X.509 Algorithm Identifiers for SLH-DSA) and
NIST FIPS 205. Supports both SHAKE and SHA-2 parameter families
across all twelve standardized variants.
DER codec:
- New PrivateKeyDecode, PublicKeyDecode, KeyToDer, PrivateKeyToDer,
PublicKeyToDer with RFC 9909 encoding (bare OCTET STRING containing
4*n raw bytes = SK.seed || SK.prf || PK.seed || PK.root, no nested
wrapper). OID auto-detection across all twelve SHAKE / SHA-2 variants.
- PublicKeyDecode raw-bytes fast path mirrors wc_Falcon_PublicKeyDecode
and wc_Dilithium_PublicKeyDecode so callers (notably
wolfssl_x509_make_der and ConfirmSignature, which pass the raw
BIT STRING contents stashed by StoreKey) decode correctly. Honours
the caller's *inOutIdx start offset.
- Error paths in Private/PublicKeyDecode preserve params/flags/
inOutIdx and only ForceZero the buffer half each helper actually
writes; skip the wipe entirely on BAD_LENGTH_E (no bytes touched).
- ImportPublic uses |= on flags so a Private-then-Public import
sequence retains FLAG_PRIVATE.
OID dispatch:
- 12 standardized NIST OIDs (6 SHAKE + 6 SHA-2) per RFC 9909. The
pre-standardization OID-collision mechanism is removed since NIST
OIDs do not collide.
- wc_SlhDsaOidToParam / wc_SlhDsaOidToCertType return NOT_COMPILED_IN
(rather than -1) for recognised SLH-DSA OIDs whose parameter set
isn't built; wc_IsSlhDsaOid recognises both. The x509 dispatch
surfaces this as a precise diagnostic instead of the generic
"No public key found".
- wc_GetKeyOID picks a placeholder parameter from whatever variant is
compiled in and #errors at compile time if none is.
- asn_orig.c EncodeCert / EncodeCertReq accept SHA-2 SLH-DSA keyTypes
alongside SHAKE.
Tests and fixtures:
- Test cert chain in certs/slhdsa/: SLH-DSA-SHAKE-128s and
SLH-DSA-SHA2-128s self-signed roots that sign reused ML-DSA-44
entity keys (server + client), plus the gen script
(gen-slhdsa-mldsa-certs.sh, OpenSSL >= 3.5).
- New TLS 1.3 .conf scenarios under tests/suites.c dispatch:
test-tls13-slhdsa-shake.conf, test-tls13-slhdsa-sha2.conf, and a
wrong-CA negative test test-tls13-slhdsa-fail.conf.
- DER round-trip and on-disk decode tests; bench_slhdsa_*_key.der
fixtures regenerated with wolfSSL's own encoder so the codec is
pinned to RFC 9909.
- New unit test test_wc_slhdsa_x509_i2d_roundtrip exercises the raw
PublicKeyDecode entry point that wolfssl_x509_make_der relies on.
- test_wc_slhdsa_check_key now tests both Public-then-Private and
Private-then-Public import orderings.
Build / ABI:
- DYNAMIC_TYPE_SPHINCS = 98 kept as RESERVED with a tombstone comment
for ABI stability; new code should use DYNAMIC_TYPE_SLHDSA (107).
- All build system / IDE project files updated; SPHINCS+ sources,
headers, and test data removed.
- Dead bench_slhdsa_*_key arrays removed from gencertbuf.pl and
certs_test.h; the .der files on disk drive the decode tests.
wolfssl/wolfcrypt/wc_slhdsa.h: implement WOLFSSL_SLHDSA_NO_SHAKE and WOLFSSL_SLHDSA_NO_SHA2, and fix WC_SLHDSA_MAX_SIG_LEN setup to reflect SHA2 variants;
wolfssl/wolfcrypt/settings.h: if WOLFSSL_KERNEL_MODE, set WOLFSSL_SLHDSA_VERIFY_ONLY unless WOLFSSL_SLHDSA_NO_VERIFY_ONLY;
wolfcrypt/src/wc_slhdsa.c: fix WOLFSSL_SLHDSA_VERIFY_ONLY to work with --enable-slhdsa=sha2,verifyonly;
fix -Wunused-variables in slhdsakey_wots_pk_from_sig_x4();
wolfcrypt/test/test.c: in slhdsa_test(), fix gating for compatibility with --enable-slhdsa=sha2,verifyonly;
tests/api/test_slhdsa.c: fix gating in test_wc_slhdsa() and test_wc_slhdsa_sizes().
ReadPemFromBioToBuffer slurps the entire BIO in one shot, so iterative
callers like wolfSSL_PEM_read_bio_X509_CRL (and by extension
wolfSSL_X509_load_crl_file's BIO branch) saw EOF after the first block
and silently dropped every CRL after the first in a multi-CRL bundle.
Refactor wolfSSL_PEM_read_bio_X509_CRL to delegate to
wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio, which already reads one
PEM BEGIN/END pair per call and leaves the BIO positioned just past the
END line. Loop over it so we skip past intervening cert/key blocks and
return the next CRL in the stream — matching OpenSSL's
PEM_read_bio_X509_CRL, verified against OpenSSL 3.0.13 with cases
{cert,CRL}, {CRL,cert}, {CRL,cert,CRL}, {key,CRL}, {CRL,key,CRL}: in
each case OpenSSL skips non-CRL blocks until EOF.
When the caller passes a non-NULL `x` whose `*x` is already populated,
free the previous CRL before overwriting the slot — matching the
d2i_X509_CRL reuse contract the old body relied on.
To keep both helpers visible at the new call site, drop their `static`
qualifier (wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio for the per-block
read, wolfSSL_X509_PKEY_free to free defensively-allocated keys parsed
from intervening non-CRL blocks). Their definitions in src/x509.c and
declarations in wolfssl/internal.h are widened from OPENSSL_ALL to
OPENSSL_EXTRA || OPENSSL_ALL so the OPENSSL_EXTRA-only build (which
compiles wolfSSL_PEM_read_bio_X509_CRL) links cleanly. The unrelated
INFO_read_bio / INFO_read_bio_X509_INFO group below them keeps its
OPENSSL_ALL gate because it depends on wolfSSL_X509_INFO_new/free that
are still OPENSSL_ALL-only.
Also register the previously-orphaned test_wolfSSL_X509_load_crl_file
(its slot in TEST_OSSL_X509_LOOKUP_DECLS was a duplicated
test_wolfSSL_X509_LOOKUP_ctrl_hash_dir entry), update its assertion for
crl2.pem (which already contains two CRLs) to expect 2 instead of 1, and
add a multi-CRL bundle case that builds a memory BIO from
crl.pem + server-cert.pem + crl2.pem and asserts that the reader walks
past the cert and returns all 3 CRLs before NULL.
- tls.c: TLSX_CertWithExternPsk_GetSize takes word16*, but length was
widened to word32 in TLSX_GetSize. Use the hsz staging variable like
the other cases so WOLFSSL_CERT_WITH_EXTERN_PSK builds compile.
- tls.c: silence -Wunused-variable for hsz in builds where every case
that consumes it (TLS 1.3, PSK, ETM, early data, PHA, cookie, cert
with extern PSK) is compiled out, e.g. user_settings_tls12.h.
- test_tls_ext.c: assert session->ticketLen > 0 before mutating
ticketAdd in the ticket-age out-of-window test so it fails loudly if
no NewSessionTicket was received.
DoClientTicketCheck's ticket-age bounds (-1000 ms low bound and
MAX_TICKET_AGE_DIFF*1000+1000 ms high bound) were never exercised by
any integration test, so mutations of the constants went undetected.
Establish a TLS 1.3 session, read the NewSessionTicket, then shift the
client's cached ageAdd by well over 1 second so the server's
unobfuscated diff falls outside the valid window on resumption. The
server must reject the PSK — session_reused stays 0.
DoTls13ClientHello enforces RFC 8446 Section 4.1.4 by comparing the
cipher suite in the second ClientHello to the hrrCipherSuite cached on
the server from the HelloRetryRequest. No existing test covers the
mismatch branch, so a deletion of the check would silently allow a
client to switch cipher suite between CH1 and CH2. Drive a partial
handshake until the server has emitted the HRR, then flip the cached
hrrCipherSuite on the server; processing CH2 must surface
INVALID_PARAMETER.
wolfSSL_TicketKeyCb is the built-in ticket callback registered by the
OpenSSL-compat wolfSSL_CTX_set_tlsext_ticket_key_cb API. Its
ConstantCompare of the ticket HMAC was never reached in any test, so a
deletion of the check would silently accept forged tickets. New test
sets up the compat callback, establishes a TLS 1.2 session, saves it,
flips a byte of the encrypted ticket, and asserts the resumption
attempt does not complete.
Cover both branches of TLSX_SecureRenegotiation_Parse's ConstantCompare
against the cached Finished verify_data: a single memio test loops
over client-side and server-side corruption, renegotiates, and
asserts the offending peer surfaces SECURE_RENEGOTIATION_E.
Tls13IntegrityOnly_Decrypt was completely untouched by existing tests,
so any mutation of its ConstantCompare would pass CI. Add a memio
TLS 1.3 handshake over TLS13-SHA256-SHA256 (integrity-only NULL cipher),
then corrupt the final byte of the next record body via an IORecv
wrapper and assert the server surfaces DECRYPT_ERROR.
Cover the Poly1305 ConstantCompare tag check in ChachaAEADDecrypt that
no existing test was hitting (VERIFY_MAC_ERROR never expected in the
suite). A memio-based TLS 1.2 handshake over
ECDHE-RSA-CHACHA20-POLY1305 completes, the server's IORecv is then
replaced with a wrapper that flips the final byte of the next record
body so the forged Poly1305 tag no longer matches. The server's
wolfSSL_read must surface VERIFY_MAC_ERROR.
Covers the HandleResumeHistory check that RFC 7627 Section 5.3 requires:
if the original session used Extended Master Secret, the server MUST
abort when a resumption ClientHello is received without EMS. The new
memio test performs a TLS 1.2 handshake with EMS, saves the session,
disables EMS on a fresh client, resumes with the saved session, and
asserts the server returns EXT_MASTER_SECRET_NEEDED_E.