Ed448 verification rejects a non-canonical signature scalar S (S >= L)
per RFC 8032, and that range check is the only guard against a malleated
signature: because L times the base point is the identity, (R, S + L)
recomputes the same R and would otherwise verify. The check had no
negative coverage, so a deletion or boundary mutation passed the suite
while all canonical KAT signatures kept working.
Add a test that signs a message, then verifies crafted signatures whose
S half equals the order, exceeds it in a high or low byte, and equals
S + L, asserting BAD_FUNC_ARG, plus an in-range wrong S asserting
SIG_VERIFY_E.
Fixes F-6777.
wc_ecc_check_key validates a public key's coordinate range, that the
point is on the curve, and its order, but the software path had no
negative coverage: the existing test only exercised a valid key and
NULL, and the off-curve case lived in the crypto-callback test, which
validates the device path rather than the software on-curve check. A
deletion of either the on-curve check or the coordinate-range checks
therefore passed the suite.
Add a test that imports secp256r1 public keys that are off the curve
and out of coordinate range, asserting IS_POINT_E and ECC_OUT_OF_RANGE_E
respectively, exercising the software validation path.
Fixes F-6620.
wc_DhSetKey_ex loads DH parameters as untrusted and validates that the
modulus is prime, but it passed no RNG, so the check fell back to a
Miller-Rabin test using the fixed small-prime bases 2 through 19. That
test is defeatable: a composite crafted as a strong pseudoprime to those
known bases passes as prime, letting an attacker supply a composite
modulus with a smooth factorization for small-subgroup recovery of the
private exponent and shared secret.
When no RNG is supplied on the untrusted path, create a temporary RNG so
mp_prime_is_prime_ex runs with random witnesses, which such crafted
composites cannot reliably pass. Named FFDHE primes still short-circuit
the check, and builds without an RNG keep the deterministic test.
Fixes F-6776.
wc_ecc_shared_secret_gen_sync ran the scalar multiplication and then
copied the x-coordinate to the output without checking whether the
result was the point at infinity. Both math backends report success for
the identity: ecc_map_ex sets x, y to zero and z to one and returns
success, and the single precision generators serialize the identity as
an all-zero x-coordinate. Either way a shared secret that computed to
infinity was handed back as an all-zero secret with a success code,
where SP 800-56Ar3 5.7.1.2 requires an error and stop.
Check the mapped point on the software path, and detect the all-zero
output after the single precision generators, returning ECC_INF_E in
both cases. The scan accumulates over the whole buffer so it does not
branch on the secret.
A key whose private value is resident in an SE050 carries no software
scalar, so the software multiply legitimately yields the identity for
it. Skip the check for those keys specifically, rather than for a zero
scalar: on a prime-order curve a zero scalar is the one way the identity
can arise, so exempting it would disable the check for the case it
exists to catch.
Fixes F-6770.
wc_Chacha_Process validated only its pointer arguments and then produced
keystream directly from the context state. A zero-initialized ChaCha
context, common for static or global storage, that received a nonce via
wc_Chacha_SetIV but never had wc_Chacha_SetKey called would encrypt with
an all-zero, attacker-predictable key and still return success. This is
the same fail-open class already guarded against in wc_Arc4Process.
Add a keySet flag to the ChaCha struct, set it in wc_Chacha_SetKey, and
return MISSING_KEY from wc_Chacha_Process when the key was never set.
Fixes F-6893.
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.
Remaining fixes from the second review round:
- keygen: falcon_compute_public's scratch buffer holds NTT(f) (private-key
material) in its tail; wc_ForceZero it before both frees (the
f-not-invertible reject path and the success path). Also zeroize the
internally allocated hwork for consistency with the tmpbuf hardening.
- sampler: falcon_sampler_z's rejection loop never consulted the sticky
PRNG error flag, so a mid-signature SHAKE256 squeeze failure could make
berexp deterministically reject and the loop spin forever. Check p.err
each iteration and bail out; the returned value is discarded since
falcon_sign_core rejects the whole signature once p.err is set.
falcon_prng_init now frees the SHAKE256 context when a later init step
fails (plugs a device-context leak in WOLFSSL_ASYNC_CRYPT builds), and
falcon_prng_refill early-returns once the error is latched instead of
re-issuing failing squeezes.
- codec: guard the bits-dependent shifts in falcon_trim_i8_encode/decode
against out-of-range widths (defense in depth; callers only pass 5..8).
- check_key: implement the cryptographic private/public cross-check that
91ebd89d7 documented as a follow-up. New falcon_native_check_key decodes
(f, g) from the private key and h from the public key and verifies the
defining relation h*f == g (mod q, mod X^n + 1) slot-wise in the NTT
domain (falcon_ntt keeps values canonical in [0, q)); a slot with
NTT(f) == 0 is rejected too, as keygen only emits invertible f.
wc_falcon_check_key dispatches to it whenever the native signing core is
compiled in, and falls back to the presence check in verify-only /
callback-only builds. Doxygen updated to the actual contract, and a unit
test added: a mismatched pair (public half from a different key) must
fail with PUBLIC_KEY_E. This also strengthens the keypair validation
done via wc_falcon_check_key in asn.c.
Four fixes from PR review:
- zephyr/CMakeLists.txt: the native port split falcon.c into wc_falcon_*.c
translation units; add the portable sources so a Zephyr build with Falcon
links. x86-64 asm/AVX2 and the NEON backend are left out (not selected by any
Zephyr config).
- configure.ac: fold the standalone --enable-falcon-{asm,double,avx2,neon}
switches into comma-separated sub-options of --enable-falcon
(e.g. --enable-falcon=avx2), matching the common wolfSSL idiom. avx2/neon
imply the double backend after arch-gating so ignoring an unsupported vector
backend does not clobber an explicit 'double'. Sweep the qemu-falcon-neon doc
to the new spelling.
- configure.ac: align the Falcon line in the two feature summaries.
- falcon.c/falcon.h: drop the duplicate public-key copy kept behind the private
key. Its only remaining reader was wc_falcon_check_key, whose compare was
against a copy of the same bytes and so could never detect a real mismatch;
wc_falcon_export_private already rebuilds the concat layout on demand. Shrink
key->k from FALCON_MAX_PRV_KEY_SIZE to FALCON_MAX_KEY_SIZE (saves 1793 bytes
per key at level 5). check_key now verifies both halves are present and
documents a full cryptographic cross-check as a follow-up. Update the unit
test that relied on the old in-memory-copy compare.
- falcon.c (wc_falcon_import_private_only): call falcon_store_pub_behind_priv
unconditionally after setting prvKeySet. The raw-size branch previously only
synced the behind-private public copy in the concat layout, so importing a
public key first and then a raw private key left key->k + KEY_SIZE zero and
made wc_falcon_check_key return a false PUBLIC_KEY_E. Added a regression case
to test_wc_falcon_check_key covering the public-then-raw-private ordering.
- wc_falcon.c (native sign cleanup): free the sampler's SHAKE256 context with
wc_Shake256_Free(&spc.p.shake) when it was initialized, before ForceZero.
Without it, WOLFSSL_ASYNC_CRYPT + WC_ASYNC_ENABLE_SHA3 builds leaked the
async device context allocated by wc_InitShake256 on every sign, unlike the
keygen and hash-to-point paths which already free their SHAKE contexts.
Falcon had crypto-level coverage (KAT + native round-trip in
wolfcrypt/test/test.c) but, unlike ML-DSA and SLH-DSA, no dedicated
tests/api/ unit test exercising the public wc_falcon_* / wc_Falcon_* API
surface. This adds one, wired into the unit test runner as the "falcon"
group.
Coverage (both Falcon-512 / L1 and Falcon-1024 / L5, which are always
compiled together):
- sizes: size/priv_size/pub_size/sig_size vs the spec constants,
get_level round-trip, and NULL / unset-level rejection.
- make_key: NULL and unset-level rejection; real keygen -> check_key.
- sign_vfy: sign -> verify; wrong-message and one-byte tamper rejected;
too-small buffer -> BUFFER_E with the required length set;
verify with no public key -> BAD_FUNC_ARG.
- import_export: public / private-only (raw) / private (concat) / export_key
round-trips, each re-signed or verified, plus too-small
(BUFFER_E) and wrong-size (BAD_FUNC_ARG) paths.
- check_key: valid pass; corrupted public copy, public-only and
private-only keys all fail (PUBLIC_KEY_E); NULL rejected.
- der: KeyToDer / PrivateKeyToDer / PublicKeyToDer round-trips via
PrivateKeyDecode / PublicKeyDecode, size-query (NULL output),
and the SetAsymKeyDer too-small contract (BAD_FUNC_ARG).
- error_paths: exhaustive NULL / bad-level / wrong-size / no-key-set
argument sanitising for every public entry point.
Tests requiring key generation or signing are gated on
WC_FALCON_HAVE_NATIVE_SIGN so the file also builds in
WOLFSSL_FALCON_VERIFY_ONLY and WOLF_CRYPTO_CB_ONLY_FALCON configurations;
size and argument-sanitising tests run in every HAVE_FALCON build.
Verified: 7/7 pass under both --enable-falcon-avx2 and the default
constant-time build; compiles clean with WOLFSSL_FALCON_VERIFY_ONLY.