wc_ed448_check_key()'s Y-range check is a walk over the bytes above the 0xFE
position followed by a single compare of that byte, so the stanza aimed at the
low-byte scan and its final byte compare no longer reaches a decision.
The three stanzas now cover both surviving decisions and both of their operands:
- every byte above the 0xFE position 0xff, that byte 0xfe: the loop runs to
exhaustion, so its index operand goes false and the compare below is
reached and taken.
- the same value with one byte inside the walked range cleared: the loop
breaks with ret == 0, covering the byte compare's true side and the
following (ret == PUBLIC_KEY_E) guard's false side.
- every byte 0xff, including the 0xFE position: no small-order table row
matches, the walk finds no byte below 0xff and the 0xFE compare is false,
so the key is rejected as out of range -- the compare's false side, and the
one stanza whose result is exact rather than curve-decode dependent.
Several DTLS tests drive a connection with reads alone and then assert that
something was sent, an ACK in most cases. With WOLFSSL_RW_THREADED that only
happens once the application asks for it, so stand in for such an application
and pump where the send is expected. The helper is a no-op elsewhere, so
builds whose read path sends for itself are unchanged.
test_dtls13_ack_overflow needs the same treatment in its setup, where the ACK
the first reads scheduled would otherwise be left in the seen-record list and
counted by the assertions that follow. It sits in the dtls13 group rather than
dtls, so a run of the dtls group alone does not cover it.
Add a test for the new API that runs in every build rather than only the
threaded one. It schedules a key update the way the AEAD failure limit does
and requires the predicate to report it, the pump to perform it and put a
record on the wire, and the wait for the peer's acknowledgement not to be
reported as work. It then drives the state that would wedge a drain loop, a
peer requesting a KeyUpdate while ours is unacknowledged, and requires pump
and predicate to agree that nothing can be sent and the request to be kept
until it can. It also covers the bad argument cases, a DTLS 1.2 object being
refused rather than quietly succeeding, and that refusing an object records no
error against the connection and leaves it usable.
The AEAD limit test excludes its second key update and its hard limit check
from threaded builds. Both need the acknowledgement processing that stays off
the write path: without it the decrypting epoch stops matching the one the
drop counter is placed on, so the read never reaches the limit and the test
hangs rather than failing.
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.
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.
Two MC/DC cases the campaign disclosed and PR 10973 fixed are now safe to
drive:
* ascon: wc_AsconAEAD128_DecryptUpdate(ctx, out, NULL, 0) demonstrates the
inSz!=0 operand (the NULL-memcpy on inSz==0 is fixed) -> ascon.c 36/36.
* rsa: the wc_CompareDiffPQ / _CheckProbablePrime / wc_CheckProbablePrime_ex
XMALLOC-chain later operands (idx1/idx2) are now faulted via arm(2)/arm(3)
in test_rsa_fault_whitebox.c; they were blocked by the partial-OOM
double-free the fix removed -> rsa.c 168 -> 172.
Add test_wc_WolfEventDecisionCoverage (group "wolfevent") driving the
wolfEvent / wolfEventQueue_* doubly-linked FIFO from the public API:
the queue==NULL || event==NULL guards (Push/Pop/Add/Remove, each operand
plus the all-false half), the Add first-element branch, the Remove
head/tail/sole cascade including the (event==head && event==tail) AND and
the defensive (next==NULL || prev==NULL) corruption guard, and the Poll
context-filter OR.
Guarded by HAVE_WOLF_EVENT (compiled empty otherwise). The queue core is
async-independent; it builds standalone (no WOLFSSL_ASYNC_CRYPT) now that
BUILD_WOLFEVENT is true under --enable-usersettings and wolfEvent_Poll no
longer warns on unused params in non-async builds.
Two check-source-text / clang-tidy fixes on the MC/DC test files:
* Wrap error-code comparison operands in WC_NO_ERR_TRACE() (check-source-text
check I). Code comparisons (blake2b/blake2s/hpke white-boxes and the
logging global-queue pull check) are wrapped; the pseudo-code in doc
comments and the WB_CHECK message strings (mcdc_fault_alloc.h, dsa/mlkem
fault white-boxes, logging white-box) are reworded so an error code is no
longer adjacent to == / != .
* Uppercase the integer-literal suffixes in test_sakke.c (384u -> 384U, etc.)
for clang-tidy readability-uppercase-literal-suffix.
No behavioral change.
test_wc_Des_CbcEncryptDecrypt drove the per-operand NULL guards of
wc_Des_CbcEncrypt/CbcDecrypt/EcbEncrypt/SetIV. The frozen FIPS/selftest
single-DES module predates those open-build NULL checks and dereferences a
NULL des/out/in directly, so the probes segfault (exit 139) in a FIPS build.
Gate the whole test on !HAVE_FIPS && !HAVE_SELFTEST -- this single-DES MC/DC
coverage is gathered in the open build; the frozen module is out of its scope.
Add test_wc_FalconDecisionCoverage to the falcon API group, covering the
public wc_falcon_* wrapper decisions (level checks, import/export and
sign/verify argument guards, init_id/init_label) with per-condition MC/DC
independence cases.
Add tests/unit-mcdc/test_falcon_whitebox.c, a standalone binary that
#includes falcon.c and drives its file-static encode/decode/zint/modp/
sampler/keygen-solver/sign guards -- including the small-mem
falcon_do_sign_dyn twin -- with both halves of each independence pair, plus
a real Falcon-512 make/sign/verify round-trip for the proceed halves.
Register the whitebox in EXTRA_DIST (test-only; it is not part of the
library build).
wc_ValidateEccsiPair() reports an off-curve PVT via wc_ecc_is_point(), whose
error code is backend-dependent: the mp-based check (classic / SP_MATH_ALL /
fast-math) returns IS_POINT_E, but the minimal WOLFSSL_SP_MATH backend routes
through sp_ecc_is_point_*(), which returns MP_VAL for a point not on the curve
(and eccsi.c only remaps -1 -> IS_POINT_E, not MP_VAL). Select the expected
code per backend so the all-pq-sp-math CI config (--enable-sp-math) passes.
Verified: full unit.test --api under --enable-all --enable-sp-math --enable-sp-asm
reports 0 failures.
- test_wolfmath.c: limit the "digits > capacity" mp_rand rejection vector to
the fixed-size backends. USE_INTEGER_HEAP_MATH grows the mp_int via
mp_set_bit instead of rejecting, so the call would legally succeed (and
force a large allocation), failing ExpectIntNE.
- test_memory_whitebox.c: guard the WOLFSSL_STATIC_MEMORY / WOLFSSL_MEM_FAIL_COUNT
defines with #ifndef so a build that already provides them (user_settings.h /
CFLAGS) does not hit a redefinition warning treated as error.
- test_sakke_whitebox.c: skip the sakke_mulmod_base_add() calls when
wc_ecc_new_point() returns NULL. That function does not validate its result
pointer and would dereference a NULL addResult under allocation pressure.
- test_hpke.c: guard both test bodies on HAVE_HPKE. They were gated only on
HAVE_CURVE25519 && !NO_SHA256 && WOLFSSL_AES_128, so configs with those but
without HPKE (e.g. pk-mlkem) compiled the body against absent HPKE symbols
and failed to build under -Werror.
- test_sakke.c: make the wc_GenerateSakkeRskTable / wc_GenerateSakkePointITable
/ wc_SetSakkePointITable checks SP-backend agnostic. The required table size
is 0 on the small-stack SP path but non-zero on the full precomputation path
(sizeof(sp_table_entry_1024) * 1167 / * 256), so the previous fixed
"len == 0" and success-with-tiny-buffer assertions failed (and could write a
full-size table into the small stack buffer) under --enable-all. Capture the
queried length and branch: the Rsk table builds into a correctly-sized heap
buffer; the PointI table's full-path build/store is left to the sakke_test
KAT (it stores the pointer in the key).
- codespell: rename addRes -> addResult in test_sakke_whitebox.c and reword a
comment in test_hpke.c ("statics").
wc_mlkem.h/test_mlkem.c: Respect WC_NO_CONSTRUCTORS guard.
settings.h, fe_operations.h: move WOLFSSL_CURVE25519_USE_ED25519 derivation into settings.h so the assembler sees it; fixes fe_cmov_table undefined on ARM32.
ge_448.c: shift the product instead of the byte in six sc448_* loops, dodging a GCC ARM32 NEON miscompile that produced wrong ed448 signatures; table shrunk [56]→[28].
* fixes for NO_DH;
* fixes in test_wc_ed448_import_public() and test_wc_Ed448DecisionCoverage() for FIPS v6;
* fixes in tests/api/test_sha3.c for KMAC keysize in FIPS builds.
Add AES-GCM (128/256) as an ECIES DEM next to the AES-CBC/CTR+HMAC modes. Only the encryption key comes from the KDF; the mac salt is bound as GCM AAD and the 16-byte tag replaces the HMAC. The GCM DEM honors all three IV build modes, and default fixed-nonce GCM is gated behind the new WOLFSSL_ECIES_STATIC_GCM_NONCE opt-in. Adds ECIES CryptoCb encrypt/decrypt, the WOLF_CRYPTO_CB ctx getters, devId/heap threading into the DEM primitives, and test/benchmark/CI coverage.
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.
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.
VerifyX509Acert parsed the acert and checked the signature but never
validated the notBefore and notAfter dates, so wolfSSL_X509_ACERT_verify
and wc_VerifyX509Acert accepted expired or not-yet-valid attribute
certificates whenever the signature was good. Call CheckDate for both
validity bounds before signature verification. CheckDate returns the
proper date error and honors the runtime skip-date control. Also correct
ParseX509Acert to report ASN_AFTER_DATE_E instead of ASN_BEFORE_DATE_E
when the notAfter date check fails.
Fixes F-6986.
wc_falcon_export_private_only and wc_falcon_export_private did not check
prvKeySet, so exporting from a key with only a level set copied the
uninitialized key->k and returned 0 -- unlike wc_falcon_export_public, which
guards on pubKeySet. Add the matching prvKeySet guard to both (before the
length check), and cover it in test_wc_falcon_error_paths alongside the
existing export_public no-key case. The prior test could not exercise the
guard because the guard did not exist.
- wc_falcon.c: replace the stale "Phase 1: verification only" file banner
(the file now holds keygen/sign/verify cores).
- falcon.c: fix the garbled wc_falcon_verify_msg doc comment (removed a
non-existent contextLen parameter; state the level-dependent BUFFER_E
bound and the *res convention).
- wc_falcon.c: name the sign compression-fit retry bound
FALCON_SIGN_MAX_ENCODE_RETRIES (was a bare 32) and document why the
bound is safe, mirroring FALCON_SIGN_MAX_RESTARTS in wc_falcon_sign.c.
- test_falcon.c: add a direct wc_falcon_import_private_only concat(priv,pub)
test that recovers the public key and signs+verifies from that single
import, covering the recover-pub-from-concat path end to end.
test_wc_falcon_sign_verify in tests/api/test_signature.c was merged to
master gated on HAVE_FALCON && HAVE_LIBOQS. With liboqs removed the
macro is defined nowhere, so the test was dead code and check-source-text
failed with 'unrecognized macros used: HAVE_LIBOQS'.
Gate it on WC_FALCON_HAVE_NATIVE_SIGN like the rest of the native
signing tests, and replace the obsolete liboqs-RNG comment. The tree
now has zero HAVE_LIBOQS references, so no .wolfssl_known_macro_extras
entry is needed.