Add Enable/DisableNormalMasterSecret APIs to enforce EMS requirement at runtime. Disable server EMS when DisableExtendedMasterSecret is called, not just client EMS.
Added tests
Restored NULL cipher suite tests into new conf files
Restored previous eNULL handling
Fix session timeout check and clear ticket when not resuming
Document WOLFSSL_TLS13_NULL_CIPHER_IN_DEFAULT
A new API test registers a finder and checks that a context with no device id is routed to the device by the finder and not routed once the finder is gone, and the os-check matrix gains an ECIES entry with the finder on so the test runs.
A context starts in software and only wc_ecc_ctx_set_dev_id() sends the ECIES callback and the KDF, AES and HMAC steps to a device; tests, benchmark, docs and the os-check matrix are updated to match.
Every build hashed each handshake message with SHA-512 and reserved two Hashes
structures for the TLS 1.2 Finished and CertificateVerify digests, whether or
not TLS 1.2 was compiled in. TLS 1.3 hashes the transcript with the cipher
suite's hash, and WOLFSSL_TLS13_SHA512 is what allows SHA-512 to be that hash,
though no cipher suite in the table selects it today. TLS 1.3 also derives
Finished and CertificateVerify differently, so a TLS 1.3 only build never reads
any of it.
Keep verifyHashes and certHashes only when TLS 1.2 is enabled, and the SHA-512
transcript only when TLS 1.2 or WOLFSSL_TLS13_SHA512 is. A TLS 1.3 only build
drops the handshake hashes from 864 to 352 bytes per handshake and stops
running SHA-512 over every handshake message. Note that ./configure
--disable-tlsv12 does not by itself define WOLFSSL_NO_TLS12, so an autotools
build gets the saving only when that macro is set.
Add --enable-tls13-sha512 and the matching CMake WOLFSSL_TLS13_SHA512 option,
so a macro that now decides the layout of the handshake hashes is recorded in
the generated wolfssl/options.h like every other one.
RFC 8446 Appendix D.4 describes a TLS 1.3 server answering a ClientHello
that carries a non-empty legacy_session_id with a ChangeCipherSpec. Both
server send paths sat behind WOLFSSL_TLS13_MIDDLEBOX_COMPAT, which only
--enable-tls13-middlebox-compat and --enable-jni define and which
neither enables by default, so a default-built server never sent one.
Peers
that require the record, such as Erlang's ssl, abort the handshake with
an unexpected_message alert.
Compile the two server accept-path sends unconditionally and drive them
from the runtime tls13MiddleBoxCompat flag, which the server already
clears when the client's session id is empty. This needs the
sentChangeCipher option bit compiled in for every TLS 1.3 build, and
SendChangeCipher itself, whose definition was guarded on TLS 1.2 or old
TLS or middlebox compat and so went missing from a TLS 1.3-only build
such as examples/configs/user_settings_tls13.h. That definition is now
compiled for a TLS 1.3 server, or a TLS 1.3 client in a middlebox compat
build, so a client-only build that can never call it does not carry it.
DTLS stays excluded by the existing guard. QUIC clears the flag on its
own rather than trusting the peer to honour the empty session id RFC
9001 Section 8.4 calls for, since a ChangeCipherSpec record has no way
to travel a QUIC CRYPTO stream. With ECH the outer ClientHello decides
on its own, since TLSX_ECH_Parse rebuilds the inner hello carrying the
outer session id, so no carve-out is needed for the inner re-parse.
wolfSSL_clear()
resets the flag and sentChangeCipher, or a reused server object would
answer only its first peer.
Under TLS 1.3 the record is a dummy and must not move handshake state,
so the OPENSSL_EXTRA block no longer assigns serverState for it. It ran
before any send could fail, so a retryable send left serverState reading
as ChangeCipherSpec complete and the server stopped waiting for the
second ClientHello. The info callback still fires on both sides, and the
client's clientState is untouched, so nothing an application can observe
changes. Both server sends go through one static helper,
SendTls13ServerChangeCipher, so the guard, the mark and the flush are
stated once, with the flush an argument because only the
HelloRetryRequest needs it. sentChangeCipher is marked on a WANT_WRITE
as well as on success, since SendChangeCipher appends the record to the
output buffer before the flush that stops short, and the mark is all
that stops a second one being built. That matters on the
HelloRetryRequest path, where the resumed accept does not come back to
the same case: the prologue drains the buffer and advances past
TLS13_ACCEPT_HELLO_RETRY_REQUEST_DONE, so the flight falls through into
TLS13_SERVER_HELLO_SENT, which asks for a ChangeCipherSpec of its own.
SendTls13ServerHello flushes a HelloRetryRequest as soon as it is
built, so the ChangeCipherSpec queued behind it needs a flush of its
own. Without one, a server grouping its messages, which every
OPENSSL_COMPATIBLE_DEFAULTS build does, leaves the record in the output
buffer and the peer sees a bare HelloRetryRequest.
The three client send sites in wolfSSL_connect_TLSv13 mark the same
way. The one answering a HelloRetryRequest also keeps buildingMsg
set when the send stops short, since the second ClientHello still has to
follow and the connect state machine must stay on that case while the
record drains; without it a refused write stranded the handshake.
What a client puts on the wire is unchanged. Without
WOLFSSL_TLS13_MIDDLEBOX_COMPAT it still sends an empty session id and no
ChangeCipherSpec of its own.
The test helper counts ChangeCipherSpec records rather than answering
whether there is one, so that a second record going out reads
differently from the one record that is owed.
test_tls13_middlebox_compat_hrr_ccs covers the HelloRetryRequest path
with messages grouped and ungrouped, and checks the flight answering the
second ClientHello carries no further record.
test_tls13_middlebox_compat_server_ccs_retry refuses the write carrying
the record and resumes the handshake, so a send that stops part way
still leaves exactly one on the wire, and
test_tls13_middlebox_compat_hrr_ccs_retry does the same on the retry
path, where the flight answering the second ClientHello must carry
none. test_tls13_middlebox_compat_client_ccs_retry refuses the client's
own record on both of its handshake flights, and
test_tls13_middlebox_compat_client_ccs_retry_early_data on the third
client send site, the early data one, where the record goes out ahead of
the 0-RTT records rather than as part of a handshake flight and a
resumed wolfSSL_write_early_data() would otherwise repeat it.
Those client-side tests need WOLFSSL_TLS13_MIDDLEBOX_COMPAT, which no
os-check-linux entry defined: only --enable-tls13-middlebox-compat and
--enable-jni set it, and configure.ac turns it back off when ECH is on,
as it is under --enable-all. The all-tls13-middlebox-compat entry is
--enable-all with ECH off and the option on, so they run somewhere.
Also correct the macro's doc comment, which claimed the feature defaults
to on, reword the --enable-tls13-middlebox-compat help text and the
matching CMake option description, which described the whole feature
rather than the client half they now control,
and drop a duplicated dtls test in the same condition.
The option set ENABLED_TLSV12 to no, which the summary printed and a couple of
derived settings read, but nothing defined WOLFSSL_NO_TLS12, so every line of
the TLS 1.2 implementation was still compiled and still reachable. The one
place configure.ac adds that define sits in the FIPS lean-aesgcm bundle and is
guarded by "TLS 1.2 is still on and the user did not ask for it", which is
false exactly when --disable-tlsv12 was passed. Only the CMake build honoured
the switch.
Define it once the bundles have had their say, so --enable-tinytls13 and the
other bundles that turn the version off get it as well. The case guard keeps
the FIPS bundles from adding it twice.
Add three os-check entries for the configurations this makes reachable:
--disable-tlsv12 on its own, the same with DTLS 1.3, and the same with the
sniffer. The one existing entry that passes the option,
dtls13-client-minimal, already defined WOLFSSL_NO_TLS12 through CPPFLAGS and
strips most of the crypto along with it, so it never covered the option's own
effect.
Compiling the version out reaches code that assumed it was always there.
ProcessCSR_ex parses one certificate_status message and TLS 1.3 reads the
chain's per-certificate entries through it, so it moves out of the version
guard; only its TLS 1.2 wrapper stays behind. BuildMessage's connection-ID
size is read by the TLS 1.2 record path alone, so it is declared with it. The
tests and examples that hard-code a TLS 1.2 method, or a helper that only
those call, are skipped without the version.
CMake rejected three of the four combinations configure does but let
WOLFSSL_OLD_TLS=yes through, defined NO_OLD_TLS behind the user's back and
reported the option as still on. Reject it there too, and drop the NO_OLD_TLS
the block used to add, which the old-TLS option itself now always supplies.
curve25519_keyagree_test and curve448_keyagree_test fill pubKey by exporting
userA's public point and importing it back, inside a block guarded on the key
export. The block that then computes a shared secret against pubKey is guarded
on the shared secret and the key import alone, so building with
NO_CURVE25519_KEY_EXPORT or NO_CURVE448_KEY_EXPORT leaves it running against a
key that never received a public point, and testwolfcrypt fails.
Require the export in the consuming block too, and add a CI entry building
wolfCrypt with the X25519 export compiled out so the combination stays
covered. That entry is wolfCrypt only, since src/internal.c and src/tls.c
call wc_curve25519_export_public_ex unconditionally and TLS therefore cannot
link without it, and it carries its own -Werror so an unreferenced static
fails the build rather than passing as a warning.
snifferWorker() looped while the shutdown flag was clear and only then
drained its queue. Reading a capture file, main() enqueues every packet
and then sets that flag and joins, normally all before the worker thread
is scheduled for the first time, so the worker woke up, saw the flag and
returned without decoding a single packet. Instrumenting the loop shows
it exiting with zero iterations and a non-empty queue, at -O0 as well as
-O1, so this is an ordering race rather than a hoisted load.
Keep going after shutdown for as long as packets are queued. The check
has to be its own helper that takes the worker's semaphore, because a
placeholder head means an empty queue: looping on worker->head alone
would spin forever on a placeholder nothing will ever fill. shutdown and
unused are polled across threads, so mark them volatile.
That alone left -keylogfile broken in this build. The sniffer's server
and secret tables are thread local, so every worker has to repeat the
setup main() did for itself, but the workers only ever called load_key()
and so reported every packet as coming from an unregistered server. Carry
the keylog path in SnifferWorker and load the secrets and create the
keylog sniffer server per worker.
Verified with --enable-sniffer CFLAGS=-DTHREADED_SNIFFTEST: the whole
sniffer test suite passes, and the multi-session captures decode the same
application data at 1, 2, 4 and 8 threads as they do without threading.
Hand the corrected plaintext length to the WOLFSSL_SNIFFER_STORE_DATA_CB
callback. The previous commit fixed the length only for the branch that
copies into the caller's buffer; the callback branch still passed the raw
record size, which spans the explicit IV, the tag or MAC and any padding.
The callback pointer already points at the start of the plaintext, so the
extra bytes run off the end of the decrypt output buffer, which leaves only
a record header of slack. AddressSanitizer reports a heap-buffer-overflow
read of 68 bytes on a TLS 1.2 CBC capture before this change and is clean
after it.
Reject a non-empty encrypt_then_mac extension in the ServerHello. RFC 7366
section 3.1 specifies empty extension_data and TLSX_EncryptThenMac_Parse
enforces it, as do the neighbouring length-constrained cases in the same
switch.
Clear the Encrypt-Then-MAC decision at the start of every ServerHello.
Nothing reset it, so a renegotiation that dropped the extension kept
stripping a MAC that was no longer there, and one that added it started
stripping while the previous cipher was still active.
Reset the decrypted flag at doMessage and gate the padSz subtraction on it.
The flag is set inside the decrypt block and was never cleared, so it
described whichever earlier record in the packet had last been decrypted.
The handshake case already guards the same value with it.
Set the decoded-data flag on the WOLFSSL_ASYNC_CRYPT drain path too. A
record that goes pending is completed inside SnifferAsyncPollQueue rather
than DecodePacket, so an async build could decrypt a capture correctly and
still report that nothing was decrypted. Send that diagnostic to stderr
rather than into the decoded output, and note the exit status in the usage
text so a wrapper is not surprised by it.
Add a TLS 1.2 CBC Encrypt-Then-MAC capture and the sniffer-gen.sh recipe
that produces it, so the fix has a regression test that can be regenerated.
The existing static RSA and IPv6 captures exercise the same path but no
script in the tree can rebuild them. The new capture covers both failure
modes: with an HMAC-SHA1 suite the old code fails the decrypt outright,
and with HMAC-SHA256, whose MAC is a multiple of the block size, it
silently decrypts the wrong byte range and emits 48 bytes where 14 are
correct.
Warn when the Encrypt-Then-MAC gate drops the TLS 1.2 CBC captures, so a
build configuration that loses coverage says so, and regenerate the keylog
reference output with a binary that carries the new feature token.
Adds wc_AesKeyWrap_Pad/wc_AesKeyUnWrap_Pad and their _ex variants plus crypto
callback dispatch, routing blocks through wc_AesEcb* so an ECB only callback works.
The PSS hook can hand back the recovered block through out/outSz/outLen.
A device that reports only a verdict leaves outLen at 0; wolfSSL then
zeroes the buffer and returns saltLen + hLen, and rejects a buffer
smaller than that with RSA_BUFFER_E. A reported length is clamped to the
buffer size, and any positive handler return maps to SIG_VERIFY_E.
Move the Ed448 sign WOLFSSL_CHECK_MEM_ZERO registration below the crypto
callback hook so the device path no longer returns past it, and guard the
RSA-PSS test callback against WOLF_CRYPTO_CB_ONLY_RSA.
Adds tests for the recovered-data, over-claimed-length and undersized
buffer paths, and an os-check config that builds the hooks under
WOLFSSL_CHECK_MEM_ZERO.
The os-check matrix had one NO_SESSION_CACHE entry, dtls13-client-minimal,
and it is client only with no session tickets. Both paths that failed to
link are server side ticket code, so nothing in the matrix covered them.
Add two entries. no-session-cache-session-ticket sets the macro directly
and enables DTLS 1.3 with WOLFSSL_DTLS_NO_HVR_ON_RESUME, covering both
call sites. It also sets HAVE_EXT_CACHE, since an external cache with no
internal one is the only configuration in which the external cache branch
of FreeSessionFromCacheOrExt() survives the preprocessor, and a build can
reach it through --enable-wpas with --enable-lowresource.
lowresource-tls13-session-ticket reaches the src/internal.c paths purely
through configure options, since --enable-lowresource defines
NO_SESSION_CACHE, which is how a user runs into this without setting any
flags by hand. It does not cover src/dtls.c, whose TlsSessionIdIsValid()
needs WOLFSSL_DTLS_NO_HVR_ON_RESUME, so the two entries are not
interchangeable.
Both disable the examples, because tests/unit.test calls
wolfSSL_get1_session() and wolfSSL_set_session(), which NO_SESSION_CACHE
compiles out. The entries are therefore compile coverage for the library.
Making the tests build in this configuration is a larger change.
RFC 8446 Section 6.2 lists failure to validate a PSK binder under
decrypt_error, but TranslateErrorToAlert() mapped BAD_BINDER to
illegal_parameter. That told the peer its ClientHello was malformed, when
in fact the message was well formed and only the binder MAC did not match.
BAD_BINDER is also returned when no offered PSK matched and the server has
no certificate to fall back to. That reuse is deliberate, because it keeps
an unknown PSK identity indistinguishable from a failed binder, so both
conditions still map to a single alert after this change. A comment at the
return site in CheckPreSharedKeys() now records the invariant at both ends.
Splitting BAD_BINDER off illegal_parameter exposed a second path that had to
move with it. FindPsk() raised PSK_KEY_ERROR, which stays mapped to
illegal_parameter, when the server callback recognised an identity carrying
a non-zero obfuscated_ticket_age. That check ran before any binder was
derived, so on a certificate-less server the two alerts would have let an
unauthenticated peer enumerate valid PSK identities without holding a key.
The check is removed rather than made to fail differently: RFC 8446 Section
4.2.11 says that for an externally established identity an
obfuscated_ticket_age of 0 SHOULD be used and servers MUST ignore the value.
Ignoring it satisfies that requirement, closes the oracle, and lets a
conformant client that sends a non-zero age complete a handshake that was
previously rejected. test_tls13_psk_age_no_identity_oracle() asserts a known
and an unknown identity produce the same alert, with a positive control that
the known run really took the matched path.
Also removes an unreachable branch in CheckPreSharedKeys(). Since commit
089f1f7c9 added the earlier !*usingPSK certificate check, the later
certificate check and its BAD_BINDER return can no longer be reached. A
build with certificates returns BAD_BINDER earlier when none is loaded, and
a NO_CERTS build returns it unconditionally. The remaining branch now falls
through to the shared exit so WOLFSSL_LEAVE() is emitted on the certificate
fallback path as well.
Adds an os-check-linux configuration, tls13-psk-no-certs. psk.yml already
covers NO_CERTS with TLS 1.3 PSK through static-psk-lowresource-tls13, but
that config disables DH, so it only reaches the psk_ke branch. This one
leaves DH enabled and covers psk_dhe_ke under NO_CERTS.
Applications that inspect wolfSSL_get_alert_history() will observe alert 51
instead of 47 for these conditions. The affected tests are updated.
The mp API variant of wc_PKCS12_PBKDF_ex builds a working buffer holding
D || S || P, but totalLen was computed as dLen + sLen and so left out the
password block entirely. The fill loop writes pLen bytes starting at
S + sLen, which is past the end of the buffer, and DoPKCS12Hash was called
with the short length so the password was never absorbed into the digest.
A build with this variant enabled therefore derived the same key for every
password over a given salt, and overflowed the heap allocation or the
static buffer by pLen bytes depending on the salt length.
Sum dLen with iLen instead, matching the non-mp variant. That corrects the
allocation size, the hash input length, the I update bound and the
ForceZero coverage in one place.
Fixes F-7306.
The DigestInfo length extraction in wc_SignatureVerifyHash was compiled
only when WOLFSSL_RSA_PUBLIC_ONLY was not defined, but the block depends
on ASN.1 support, not on private key support. In a public only build the
RSA verify path stays reachable and wc_SignatureVerify still DER encodes
the digest, so the length check fell through to the plain digest size
comparison and rejected every valid signature with BAD_LENGTH_E. The
same guard also broke compilation with NO_ASN and RSA enabled, since
asn.h is not included in that case and GetSequence and GetOctetString
are undeclared.
Guard the block on NO_ASN instead, matching the DER encode call site in
wc_SignatureVerify.
Fixes F-7412.
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.
WOLFSSL_CERT_SIGN_CB is set only by --enable-certsigncb, and that flag
appears nowhere under .github/. wc_SignCert_cb() is therefore compiled by
no CI job, and neither is test_wc_SignCert_cb(), the only test covering
it. The buffer bounds check the preceding commits add to that function,
and the test assertions that go with it, would have merged without
anything building them.
Added as its own entry in the os-check Linux config list, in sorted
position. "minutes" is seeded at 8.3 from the sibling --enable-all entries
rather than omitted: the omission defaults it to 1.0, which sorts an
eight minute build last in a list scheduled longest-first and deals it
into whichever shard is already fullest. It also suppresses the stale
estimate annotation that would otherwise prompt the refresh. Replace it
with the real number from the Minutes column of the first run.
Verified by running the entry through the workflow's own driver,
.github/scripts/parallel-make-check.py, with the CFLAGS the workflow
applies at make time (-pedantic -Wdeclaration-after-statement
-Wnull-dereference -Wno-overlength-strings
-DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE) and --private-dir=certs.
The out-of-tree build produces no compiler warnings and make check
reports 17 passed, 6 skipped, 0 failed. Both test_wc_SignCert_buffer_bounds
and test_wc_SignCert_cb run rather than skip.
pq-all and trackmemory restore a seed ccache that already sits at its
cap, so every object a run compiles evicts a seeded one that is still
needed. Both reported a cache 100% full with over 200 cleanups and a
43-44% hit rate, and their configs took 3-5x their warm time. That left
pq-all shards finishing at 23-26 min against a 30 min limit: a healthy
shard passed with 5.6 min to spare, so any hiccup ahead of the build
took the job down. One did, twice, when the prebuilt deb image came up
short and the dependency step fell back to apt for 9.7 min - the only
two jobs out of 47 whose install ran past 2 min are the only two that
timed out. Size both caches to hold the seeded objects and a run's own
alongside them, and widen the limits so a slow install is survivable.
Raise the trackmemory limit as well, for a different reason: its
wolfEntropy configs have overrun it outright, once with six configs
done inside 8 min while all-wolfentropy ran on for another 32. That is
a stall rather than a slow run - 91 jobs have a 17.2 min median and a
17.9 min p90, with nothing between 18 min and the cap - so the higher
limit is headroom, not a fix. It costs nothing on a healthy run.
Recalibrate "minutes", which drives longest-first scheduling and shard
balancing. pq-all and fips-dev-no-post had drifted several-fold in both
directions: two frodokem cross-builds were declared at 4 min but take
0.3, so the pool kept scheduling them first as its longest work. pq-all
is calibrated from runs whose ccache actually hit, which the resized
cache makes the normal case; a value read off a cold run makes every
warm run warn instead. os-check-linux was already calibrated cold and
correct, so only its three genuine outliers change.
Both config lists are re-sorted to restore the documented descending
order, so most of their diff is a mechanical move rather than a value
change. Shard totals come out at 29.4/29.6 for pq-all and 76.9-77.7
across the four os-check shards.