With WOLFSSL_RW_THREADED the read path performs no scheduled work, because
transmitting from the reader would race the write thread over the output
buffer and the sending key schedule, neither of which is covered by a lock.
Post-handshake the only remaining consumers are on the write path, and
wolfSSL_dtls_retransmit() only helps while the handshake is unfinished. An
application that reads without writing therefore never acknowledges a
NewSessionTicket, KeyUpdate or connection ID message, and the peer keeps
retransmitting what it is waiting to have acknowledged. RFC 9147 relies on
those ACKs, so this is a protocol level break rather than a missed
optimisation.
Add wolfSSL_dtls13_do_scheduled_work() so such an application can send that
work from its write thread, and wolfSSL_dtls13_pending_work() so it can tell
when there is any.
Both entry points ask the same helpers rather than each testing conditions of
their own, so they cannot drift into the predicate promising work the pump
then declines or silently discards, which would leave a drain loop spinning or
mislead the caller about what happened. That covers key updates in particular:
none is sent while one of ours is unacknowledged, since DTLS must not have two
in flight and Tls13UpdateKeys() drops a locally scheduled one in that state,
and a peer request is kept rather than dropped until it can be answered. The
predicate also errs towards reporting work when it cannot tell, so a loop
surfaces the error rather than stopping silently.
Refusing an object is treated as a usage error and leaves ssl->error alone.
That field is sticky, since SendData() only clears it for WANT_WRITE, pending
async work and the DTLS MAC and decrypt cases, and wolfSSL_write() skips its
write-dup drain while it is set, so recording one would disable the very drain
a write-dup application depends on.
Write-dup pairs are out of scope on both sides. They park the read side's work
in the shared WriteDup struct, which only wolfSSL_write() reconciles, so they
already have a drain. Completing a key update we started ourselves is out of
scope too: that needs the peer's acknowledgement processed, which rotates the
sending keys and creates an epoch, and the epoch table has no locking while
the read thread mutates it as well.
The declaration is gated to match where the definitions live, so a lean build
is not promised a symbol it does not get.
The encap and decap paths create a temporary RNG, install it into a key that
belongs to the caller so the shared secret computation can blind, and then
free it without taking it back out. Both wc_ecc_set_rng and
wc_curve25519_set_rng only record the pointer, and nothing else ever writes
that field, so the caller's key was left pointing at freed memory. Encap does
this to the ephemeral key and decap to the receiver key, including the
curve25519 branch. Repeated HPKE calls hide it because each one installs a
fresh RNG first, but any other use of the key that consults it, such as an
ECDH or a signature under timing resistance, reads the freed object. ECH
holds exactly such a long lived key.
Save whatever RNG the key already had and put it back before freeing the
temporary one, so the key is handed back to the caller unchanged. Clearing
the field instead would silently drop an RNG the caller had installed for
blinding of their own.
Extend the HPKE round trip test to give both keys an RNG of its own and to
require them to still have it once the seal and open have finished.
Fixes F-7083.
SSL_clear recycles a WOLFSSL object for a new connection, which is the usual
pattern in connection pooling servers, and wolfSSL_shutdown calls it on
success as well. It reset the option and state fields but left every piece of
key material from the previous connection in place. The teardown path in
SSL_ResourceFree is careful here and force zeroes the keys struct and the TLS
1.3 traffic secrets, so a reused object ended up holding material that a
freed one would not.
The keys struct keeps the write keys, MAC secrets and IVs, clientSecret and
serverSecret keep the TLS 1.3 traffic secrets, the DTLS 1.3 epoch table keeps
traffic keys, IVs and sequence number keys for every epoch, and the handshake
arrays keep the master secret, the pre master secret, the PSK key and the TLS
1.3 key schedule secret. The tls-unique fields keep the Finished values of the
connection that just ended, so the next caller could bind to the wrong
session. The buffers are sized for the largest supported algorithm, so a later
handshake that negotiates something smaller only overwrites a prefix and the
tail survives.
Force zero all of it. A freshly created object has these zeroed already, with
two exceptions that are put back after the wipe: the multicast peer identifier
sentinel, and the unprotected DTLS 1.3 epoch 0 together with the epoch
pointers aimed at it, which only InitSSL sets up and without which the next
handshake has no valid epoch.
Wipe the handshake arrays in place rather than releasing them. They have to
stay allocated because wolfSSL_set_secret, the exporter and the accessors that
run after a connection all read from them on an object that is being recycled
rather than freed, and because the key agreement routines take preMasterSz as
the size of the buffer they may write, so that is restored to what a freshly
allocated Arrays carries. An application that asked to keep the arrays still
gets back everything the API can hand it, so the master secret and the
exporter secret only go when it did not ask, while the pre master secret, the
PSK key and the key schedule secret always do because nothing reads those
back.
wolfSSL_set_secret and wolfSSL_make_eap_keys both reached into the arrays
without checking that they are there, which the ordinary handshake teardown
can already leave them not to be, so both now report a bad argument instead.
Add a regression test that runs a handshake, clears the object with the arrays
kept, and requires the write keys, both traffic secrets and the pre master
secret to be gone while the master secret, the exporter secret and the client
random survive. It then takes that request back, clears again, and requires
the master and exporter secrets to be gone with the arrays themselves still
present.
Fixes F-7258.
WOLFSSL_HMAC_CTX keeps a copy of the inner and outer pads outside the
embedded wolfCrypt HMAC object so that a later init with a NULL key can
restore the key. Those pads are the key combined with the fixed padding, so
for any key no longer than the hash block size the key falls out of a single
exclusive or. Cleanup only called wc_HmacFree on the embedded object, which
zeroes what it is given but cannot reach the enclosing context, so the saved
pads survived. HMAC_CTX_free then returned that heap block to the allocator
with the key material still in it, where it stayed until some later
allocation happened to overwrite it.
Wipe both saved pads in wolfSSL_HMAC_cleanup, which HMAC_CTX_cleanup,
HMAC_CTX_reset and HMAC_CTX_free all reach. Do the same on the set key
failure path in the init function, since the context is reported as unkeyed
there while the previous key's pads would otherwise remain.
The session ticket key callback had the same leak for the same reason. It
holds a WOLFSSL_HMAC_CTX on the stack, hands it to the application to be
keyed with the long lived ticket HMAC key, and then only freed the embedded
object, leaving the pads on the stack after every ticket encrypt and every
ticket decrypt including the error paths. Have it clean up through
wolfSSL_HMAC_CTX_cleanup so it picks up the wipe.
Add a regression test that keys a context, checks the pads were populated,
runs cleanup and requires both arrays to be zero.
Fixes F-7256 and F-7257.
wolfSSL_X509_check_host takes an explicit length and its own validation
accepts a buffer with no NUL terminator, since only an embedded NUL is
rejected and a trailing one is merely stripped when present. The iPAddress
check then called CheckIPAddr, which drops the length and measures the
buffer with XSTRLEN, reading past the end of a caller supplied buffer that
is length delimited rather than terminated. This ran on every call, not
only when checking an IP address, and is compiled in whenever
WOLFSSL_IP_ALT_NAME is defined, which OPENSSL_ALL and WOLFSSL_QT enable.
Call CheckHostName directly with the caller's length and the IP flag set.
That is what CheckIPAddr does internally, minus the length being recomputed.
Behaviour is unchanged for NUL terminated input, because the normalization
above already leaves chklen equal to the string length in that case. It
also fixes a matching bug, since a length delimited IP address sitting in a
longer buffer no longer fails to match an iPAddress entry.
Add a regression test covering an interior slice of a longer buffer and a
buffer sized exactly to the name with no terminator.
Fixes F-7248.
X509PrintDirType derived the length of the alt name payload with XSTRLEN.
A directoryName entry holds raw DER, which routinely contains zero bytes,
and under WC_ASN_NO_HEAP the buffer is not NUL terminated at all, so the
computed length was wrong in general and reading it was already out of
bounds in the no-heap case. The length then fed an unsigned loop bound of
src_len - 5, so any entry that XSTRLEN measured as shorter than five bytes
wrapped the bound to nearly UINT32_MAX and the tag scan read far past the
end of the entry. A certificate with a short or empty directoryName alt
name reaches this from the ordinary certificate printing path.
Take the length from entry->len, which the parser already fills in, and
return early when the entry is too short to hold an OID, a tag and a
length. The scan is unchanged for entries of five bytes or more, and the
ASN parsing helpers now receive the true buffer length as their bound.
Add a regression test that prints a certificate carrying a directoryName
with an embedded zero byte before the common name, and one carrying a
directoryName too short for the tag scan.
Fixes F-7223.
wolfSSL_dtls_set_pending_peer acquired the DTLS peer lock for reading and
then mutated the shared state under it. It frees and clears pendingPeer,
calls SockAddrSet which frees and reallocates the address buffer, and
writes processingPendingRecord. A read lock allows several holders at once,
so two threads in this function can both free pendingPeer.sa, and a reader
in wolfSSL_dtls_get_peer or the wolfio send path can observe a dangling
pointer.
Acquire the lock for writing instead, matching wolfSSL_dtls_set_peer, which
already does so for the same class of mutation. Only affects builds that
define WOLFSSL_RW_THREADED, since the locking is compiled out otherwise.
Fixes F-7222.
TimingPadVerify passes (pLen - macSz - padLen - 1) to ssl->hmac and relies
on the callee recovering the record length by modular addition. TLS_hmac now
does that addition with overflow checking and returns BUFFER_E before hashing
anything, so a record whose padding length byte exceeds pLen - macSz - 1 is
rejected without a MAC being computed at all, while a smaller padding byte
gets the full constant time HMAC. The padding length byte is taken straight
from the decrypted record, so this hands an attacker a Lucky13 style timing
oracle worth an entire HMAC.
Clamp the padding length in constant time before it is used, so the length
handed to ssl->hmac never wraps and every value of the padding length byte
results in the same amount of hashing. The rejection decision is unchanged,
since MaskPadding already flags an out of range padding length. The overflow
check in TLS_hmac stays as a backstop for genuinely bogus sizes.
Add a regression test that drives TimingPadVerify over every padding length
byte with a recording MAC callback and asserts the callback is always invoked
with a length that does not wrap.
Fixes F-7240.
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.
Adds public entry points mirroring the existing X.509 unknown extension callback so callers can register a handler for unrecognized CRL extensions instead of failing with ASN_CRIT_EXT_E.
The OpenSSL compatibility APIs copied caller-supplied strings into the
outbound OCSP request verbatim, so an embedded newline split it into
attacker-chosen header lines, and a header name starting with SP or HTAB
folded into the preceding header's value. OpenSSL rejects these; the
library's own fetch path already rejects CR/LF via wolfIO_DecodeUrl().
* wolfSSL_OCSP_REQ_CTX_http(): reject CR/LF in op and path.
* wolfSSL_OCSP_REQ_CTX_add1_header(): reject CR/LF in name and value.
* wolfSSL_OCSP_REQ_CTX_add1_header(): reject a name starting with SP or
HTAB, which RFC 7230 Section 3.2.4 treats as an obs-fold continuation
of the previous header rather than a new header.
* wolfSSL_OCSP_parse_url(): reject CR/LF anywhere in the URL, the likely
source of a tainted path via a certificate's AIA extension.
Fixes a heap-use-after-free found by ASAN in the sanitize-asan CI
config. wolfSSL_X509_verify_cert filled ctx->chain with borrowed
pointers while CleanupStoreCtxCallback frees the chain members it
expects from X509_STORE_CTX_get_chain. Calling X509_verify_cert from a
cert verify callback, like OpenVPN does, freed the certs twice. Take a
reference for each cert pushed onto the chain and free the members
whenever the chain is freed, like OpenSSL.
Also address review comments:
- Pass a cert verify callback failure on to the following verify
callbacks as preverify_ok = 0.
- Only define X509_STORE_CTX_set0_crls with HAVE_CRL.
OpenVPN master keeps CRLs in its own stack and passes them to each
verification with X509_STORE_CTX_set0_crls from its cert verify
callback. CRLs are no longer loaded into the store.
- Add wolfSSL_X509_STORE_CTX_set0_crls. The ctx borrows the stack.
- Check the ctx CRLs in X509StoreVerifyCert. They can revoke a cert the
CertManager accepted and can satisfy the CRL requirement when the
CertManager has no CRL loaded. The check runs after the date override
handling so that a revocation is not masked by an overridden date
error. A stale CRL in the stack does not fail the check when another
CRL vouches for the cert.
- Add CheckCertCRLFromCm to check a cert against a caller-owned CRL
using the cm of the store for CRL signature verification. The CRL
object is not modified and the cached verification result of the
entries is not used because it is only valid for the owning cm.
- Pass the good result of the cert verify callback to the following
verify callbacks in DoVerifyCallback. In OpenSSL the cert verify
callback replaces chain verification so the verify callbacks only see
its result. OpenVPN needs this to run its per-cert verification.
- Re-add OpenVPN master to CI testing.
RFC 9147 section 5.6.1 states that EndOfEarlyData is not used in DTLS 1.3
and that a receiver must terminate the connection with an
unexpected_message alert. Dtls13CheckEpoch grouped end_of_early_data into
the default case that returns SANITY_MSG_E without sending any alert, and
the DTLS 1.3 handshake dispatch in DoProcessReplyEx did not send a fatal
alert on error the way the DTLS 1.2 path does, so the connection was
dropped silently. Add an explicit end_of_early_data case that sends the
unexpected_message alert, and mirror the DTLS 1.2 SendFatalAlertOnly
handling in the DTLS 1.3 dispatch so other handshake errors are also
reported rather than dropped silently.
Fixes F-6987.
WOLFSSL_ALPN_CONTINUE_ON_MISMATCH makes a server continue the handshake
without an agreed protocol when no ALPN protocol matches, like OpenSSL,
instead of sending the fatal no_application_protocol alert that RFC 7301
section 3.2 requires. This is an explicit, caller-selected opt-in:
wolfSSL_UseALPN rejects a call that sets neither mismatch option, and the
library never enables it implicitly. Document the RFC non-compliance at
the option enum, the wolfSSL_UseALPN options parameter, and the mismatch
handling in ALPN_find_match so the trade-off is clear at every point a
user or maintainer encounters it. No behavior change.
Fixes F-7003.
The SecureRenegotiation extension embeds a Keys tmp_keys copy of the
session cipher and MAC keys, which are the keys used for the renegotiated
epoch. SCR_FREE_ALL freed that struct with a bare XFREE, leaving a full
set of session keys intact in freed heap memory. Wipe the struct with
ForceZero before freeing it, matching the ForceZero of ssl->keys on
connection teardown.
Fixes F-7008.
InitSuitesHashSigAlgo added the ecdsa_sha1 and rsa_pkcs1_sha1 signature
schemes to the signature_algorithms list based only on the build flags,
ignoring the negotiated protocol version. Because that list is also the
set a peer's signatures are validated against, any build with old TLS
compiled in advertised and accepted SHA-1 handshake and certificate
signatures for TLS 1.2, which RFC 9155 deprecates. Gate the SHA-1 schemes
on the negotiated version so they are offered only for TLS 1.0 and 1.1
handshakes, unless WOLFSSL_ALLOW_TLS_SHA1 is defined to opt back in. The
same gate excludes them for TLS 1.3, as required by RFC 8446.
Fixes F-6991.
The sniffer elided all session, server, and keylog secret list locking
whenever HAVE_C___ATOMIC was defined, assuming those tables are
thread-local and need no mutex. Thread-locality is actually governed by
THREAD_LS_T, which is only a real qualifier when HAVE_THREAD_LS is set
and NO_THREAD_LS is not. A build with HAVE_C___ATOMIC and NO_THREAD_LS
left the tables as process-shared globals with no synchronization, so
concurrent packet processing raced on the list pointers and could free a
session another thread was still using. Gate the lock elision on both the
atomic fast path and real thread-local storage, and take the mutexes
whenever the tables are process-shared. The stats counter path is
unchanged because it uses a genuine atomic add.
Fixes F-6983.
In the WOLF_CRYPTO_CB_ONLY_ECC path of wolfSSL_ECDSA_do_verify, a
non-positive length from i2d_ECDSA_SIG (an encoding failure) skipped
the verify block and left ret at its initial value of 1, reporting a
valid signature although no verification was performed. Map the
encoding failure to WOLFSSL_FATAL_ERROR so it is not treated as a
successful verification.
Fixes F-6981.
These compile-time flags are off by default and RFC-non-conformant when
enabled. Document their scope and intent at the asn.c macro list and each
enforcement site (ParseCertRelative, VerifyCRL_Signature, AddCA,
ProcessPeerCerts) so their opt-in nature is clear. No behavior change.
Fixes F-6984 and F-6985.
Move the AVX2 (__m256d+FMA) and AArch64 NEON FFT intrinsics, plus the three
internal seam headers (wc_falcon_{fpr,fft,poly}.h), into falcon.c so the native
implementation is one translation unit -- matching how sha256.c and chacha.c
carry their SIMD backends inline (only hand-written .S stays a separate file).
The blocks are gated by WOLFSSL_FALCON_FFT_AVX2 / _NEON and need no per-file
CFLAGS: AVX2 self-annotates every function with target("avx2,fma") and NEON is
AArch64-baseline. Per-backend FPC_* macros are undef'd before redefinition, as
sha512.c does for SHA_METHOD. Only the generated fpr x86-64 asm remains a file.
Merge the nine wc_falcon_*.c cores into wolfcrypt/src/falcon.c, matching the
one-file-per-algorithm convention (ed25519.c, wc_mldsa.c). Sign and keygen are
gated internally on WOLFSSL_FALCON_VERIFY_ONLY and the internal helpers become
static. The fpr/fft/poly primitives stay external (shared with the separate
AVX2/NEON FFT backends) so their three headers are retained; the other five
internal headers are removed.
Add wc_falcon_fft_neon.c, the 2-wide-double counterpart of the AVX2 FFT
backend: it processes two doubles per 128-bit vector with fused multiply-add
for the complex butterflies (falcon_FFT / falcon_iFFT), over the inline-double
fpr backend. Enabled with --enable-falcon-neon (AArch64; implies
--enable-falcon-double). Advanced SIMD is part of the ARMv8-A baseline, so no
special -march is needed; the scalar FFT/iFFT in wc_falcon_fft.c are excluded
under WOLFSSL_FALCON_FFT_NEON, and only the tail level (ht/t == 1) falls back to
scalar. Like the AVX2 backend, it does not promise bit-identical (no-FMA)
results; that is safe for the signing FFT (the sampler's determinism and
verification are unaffected).
Tested under qemu-system-aarch64 -machine virt (cortex-a53) via the new
IDE/qemu-falcon-neon bare-metal harness: a full keygen -> sign -> verify
round-trip at levels 1 and 5 accepts genuine signatures and rejects tampered
ones (NEON_FFT_PASS), with vector fmla v.2d confirmed in the FFT.
Falcon was the last algorithm backed by liboqs; now that wolfCrypt has a
native Falcon implementation, liboqs is no longer needed. Remove the
integration entirely so liboqs does not appear as a build or SBOM dependency:
- configure: drop --with-liboqs (and the -loqs link), the BUILD_LIBOQS
conditional and the summary line.
- CMake: drop WOLFSSL_OQS, the duplicate liboqs-backed WOLFSSL_FALCON
option, the OQS cross-validation / find_package(OQS) block, the
FindOQS.cmake module, BUILD_OQS_HELPER, and HAVE_LIBOQS from options.h.in.
- Remove the wolfcrypt/src/port/liboqs port layer (liboqs.c/.h) and its
wolfSSL_liboqsInit/Close calls in wc_port.c.
- settings.h: drop HAVE_LIBOQS from the asym key import/export aggregates
(HAVE_FALCON already covers them) and from the experimental gate; add
HAVE_FALCON to the experimental gate so the unstandardized Falcon requires
WOLFSSL_EXPERIMENTAL_SETTINGS in every build system.
- Drop liboqs.c from the VS/Zephyr/INTIME project files, remove the liboqs
install from Docker, and update INSTALL/tls.c text (Falcon is native now).
No functional change to non-Falcon builds; the library links no liboqs.