wc_ecc_import_x963_ex2 only checked whether an imported public point
lies on the intended curve when both USE_ECC_B_PARAM was compiled in
and the caller passed untrusted=1. In a default ./configure build,
USE_ECC_B_PARAM is not defined, so the check was compiled out entirely.
Additionally, the legacy wrapper wc_ecc_import_x963_ex unconditionally
passed untrusted=0, meaning ECIES (wc_ecc_decrypt), PKCS#7 KARI, and
the EVP ECDH layer never triggered the check even when the macro was
present. In the OpenSSL compatibility layer, wolfSSL_ECPoint_d2i
guarded its on-curve check behind !wolfSSL_BN_is_one(point->Z), but
wc_ecc_import_point_der_ex always sets Z=1 for uncompressed points,
making the check dead code.
An attacker who can supply an EC public key (e.g. via an ECIES
ciphertext, PKCS#7 enveloped-data, EVP_PKEY_derive, or
EC_POINT_oct2point + ECDH_compute_key) can choose a point on a twist
of the target curve with a smooth-order subgroup. Each ECDH query
leaks the victim's static private scalar modulo a small prime; CRT
reconstruction across enough queries recovers the full key
(Biehl-Meyer-Müller invalid-curve attack). Static-key ECIES and PKCS#7
KARI are directly affected; TLS is affected in default builds because
the USE_ECC_B_PARAM gate defeated the untrusted=1 flag that the
handshake does pass.
Four changes close the attack:
1. Remove the USE_ECC_B_PARAM gate completely in the code base so that
wc_ecc_point_is_on_curve() is compiled in all builds, not only
those with HAVE_COMP_KEY or OPENSSL_EXTRA (only set for legacy FIPS
builds in settings.h).
2. wc_ecc_import_x963_ex: pass untrusted=1 to wc_ecc_import_x963_ex2
so that ECIES, PKCS#7 KARI, and EVP callers that go through the
four-argument wrapper always validate the imported point.
3. wc_ecc_import_x963_ex2: use the lightweight sp_ecc_is_point_NNN
helpers (curve-equation check only) instead of sp_ecc_check_key_NNN
(which additionally performs a full point*order scalar multiply).
For prime-order curves (P-256, P-384, P-521, SM2) the on-curve
equation check y^2 = x^3 + ax + b is sufficient to defeat
invalid-curve attacks — every non-identity point on a prime-order
curve has the full group order, so the expensive order-multiply
check is unnecessary. This avoids the ~50% ECDH performance
regression caused by the redundant scalar multiplication.
4. wolfSSL_ECPoint_d2i (pk_ec.c): add unconditional on-curve
validation via wolfSSL_EC_POINT_is_on_curve after import. The
existing check was gated on !wolfSSL_BN_is_one(point->Z) and
therefore dead code for all uncompressed-point imports. This closes
the OpenSSL compat layer attack path (EC_POINT_oct2point followed
by ECDH_compute_key).
Non-SP curves fall back to wc_ecc_point_is_on_curve which performs the
same equation check using mp_int arithmetic.
Reported by: Nicholas Carlini (Anthropic) & Thai Duong (Calif.io)
* wc_rng_bank_default_set()
* wc_rng_bank_default_checkout()
* wc_rng_bank_default_checkin()
* wc_rng_bank_default_clear()
* Added additional argument error checking to existing APIs, with a new
rng_inst_matches_bank() helper function.
* Implemented feature gates WC_RNG_BANK_DEFAULT_SUPPORT and
WC_RNG_BANK_NO_DEFAULT_SUPPORT. When WC_RNG_BANK_DEFAULT_SUPPORT, the new
APIs are available, and a NULL bank passed to APIs implicitly refers to the
default bank.
wolfcrypt/test/test.c: in random_bank_test() add comprehensive smoke test coverage of new APIs and argument checking.
wolfssl/wolfcrypt/wc_port.h and wolfcrypt/src/wc_port.c:
* Add wolfSSL_RefInc2(), wolfSSL_RefDec2(), wolfSSL_RefWithMutexInc2(), and
wolfSSL_RefWithMutexDec2(), returning the atomically determined new count in
the second arg;
* Fix type of second arg in the fallback definition of
wolfSSL_Atomic_Ptr_CompareExchange().
linuxkm/lkcapi_sha_glue.c:
Refactor the _REGISTER_HASH_DRBG / _REGISTER_HASH_DRBG_DEFAULT facility around
the new wc_rng_bank_default facility, eliminating post-init use of
kernel-native crypto_default_rng, crypto_get_default_rng(), and
crypto_put_default_rng(), and eliminating all use on kernel 7.1+ (where these
will become unexported kernel-native statics). With the refactor, the
LINUXKM_DRBG_GET_RANDOM_BYTES facility uses only direct native wolfCrypt
objects and calls to fulfill requests.
wolfssl/wolfcrypt/error-crypt.h, wolfcrypt/src/error.c, wolfcrypt/test/test.c, tests/api.c: add WC_SUCCESS = 0 "wolfCrypt generic success".
* add WC_FIPS_186_4, WC_FIPS_186_4_PLUS, WC_FIPS_186_5, and WC_FIPS_186_5_PLUS feature macros.
* add support for WC_HASH_CUSTOM_MIN_DIGEST_SIZE, WC_HASH_CUSTOM_MAX_DIGEST_SIZE, and
WC_HASH_CUSTOM_MAX_BLOCK_SIZE, for use with custom digest algorithms.
* add SigOidMatchesKeyOid() helper function and WC_MIN_DIGEST_SIZE macro.
* add additional size and OID agreement checks for sig gen and verify ops.
* update ecc_test_vector() with FIPS 186-5 vectors.
Co-authored-by: Tobias Frauenschläger <tobias@wolfssl.com>
wc_PKCS7_DecodeAuthEnvelopedData() accepted an attacker-controlled GCM tag
length from the mac OCTET STRING and did not validate it against the
parsed aes-ICVlen parameter. In parallel, wc_AesGcmDecrypt() accepted
very short tags on decrypt while encrypt enforced WOLFSSL_MIN_AUTH_TAG_SZ.
This made short-tag verification reachable through CMS AuthEnvelopedData
and weakened integrity checks by allowing tag truncation.
Fixes:
- validate parsed macSz range in AuthEnvelopedData decode
- require authTagSz to match parsed macSz
- reject undersized GCM tags in PKCS7 decode
- enforce WOLFSSL_MIN_AUTH_TAG_SZ in wc_AesGcmDecrypt() and
wc_AesGcmDecryptFinal()
Also add a regression test in pkcs7authenveloped vectors that truncates
the final MAC OCTET STRING length from 16 to 1 and verifies decode fails.
Reported by: Nicholas Carlini (Anthropic) & Thai Duong (Calif.io)
fixes and workarounds for clang-tidy complaints:
* clang-diagnostic-unknown-warning-option
* bugprone-sizeof-expression
* clang-diagnostic-error "address argument to atomic operation must be a pointer to a trivially-copyable type"
* bugprone-macro-parentheses
* clang-diagnostic-unused-but-set-variable
* readability-redundant-declaration
Re-implemented wc_PKCS12_PBKDF() to not use MP. Added tests to
unit.test.
sp_int.c:
Fixes to comments.
Added more define build options documentation to top of file.
Fixes for builds with WOLFSSL_SP_INT_NEGATIVE defined.
Fixes for when a->used is 0 and no underflow - not actually a problem
but cleaner code.
sp_sub has different checks on a->used when values are only positive.
sp_dic_2d missing check for e less than zero.
sp_to_unsigned_bin_len_ct: remove redundant check of outSz. Change i
to int to handle a->used of 0 and make code tidier.
Configuration testing fixes.
Fix formatting in test.c.
Added 128-bit types word128 and sword128 for cleaner PKCS#12 code.
wolfcrypt/src/wc_slhdsa.c:
* refactor SAVE_VECTOR_REGISTERS2() in slhdsakey_fors_sign() as
CAN_SAVE_VECTOR_REGISTERS(), with local save-restore wrappers around the
rest of the vector calls deeper in the call stack, to avoid failing
GFP_ATOMIC allocations and long spans with interrupts disabled.
* fix numerous bugprone-macro-parentheses and bugprone-signed-char-misuses.
* use readUnalignedWord64() in SHAKE256_SET_SEED_HA_X4_*() and
slhdsakey_shake256_set_seed_ha_x4() to avoid benign unaligned access warnings
from sanitizers.
wolfcrypt/test/test.c:
* in TestDumpData(), use WOLFSSL_DEBUG_PRINTF(), not fprintf(stderr, ...), for
portability.
* in slhdsa_test_param() and slhdsa_test(), use WC_DECLARE_VAR() and friends
for SlhDsaKey allocations, and use ERROR_OUT() and single-return-point
refactors to fix error path memory leaks.
### `wolfssl/internal.h`
- **`InternalTicket` struct gains a flexible array member**: A new `peerCert[]` field (with a preceding `peerCertLen[2]`) is added to `InternalTicket`. This allows the peer's DER-encoded certificate to be stored directly inside the session ticket.
- **`ExternalTicket` struct becomes variable-length**: The `enc_ticket` field is changed from a fixed-size array to a flexible array member (`byte enc_ticket[]`). The `mac` field is removed from the struct — the MAC is now placed dynamically after the encrypted data in `enc_ticket`.
### `src/internal.c`
- The `GetRecordHeader` function now only adds `MAX_COMP_EXTRA` to the maximum allowed record size when `ssl->options.usingCompression` is true, tightening the length validation. The max fragment length extension check is now much stricter.
- **Peer certificate is serialized into the ticket**: During ticket creation, the code attempts to find the peer certificate from `ssl->peerCert` or from `ssl->session->chain` (fallback). If found and within `MAX_TICKET_PEER_CERT_SZ`, it's copied into `it->peerCert`. DTLS is explicitly excluded (peer cert length set to 0) to keep ticket size small for MTU constraints. If `HAVE_MAX_FRAGMENT` is defined and max fragment is not `MAX_RECORD_SIZE` for TLS 1.3, the cert is also skipped since `SendTls13NewSessionTicket` doesn't support fragmentation yet.
- **Peer certificate restoration from ticket**: On successful ticket decryption, if the ticket contains a peer certificate (`peerCertLen > 0`), it is decoded back into `ssl->peerCert` via `ParseCertRelative`/`CopyDecodedToX509`, and also added to `ssl->session->chain` via `AddSessionCertToChain`.
- The `CLEAR_ASN_NO_PEM_HEADER_ERROR` macro was rewritten to loop and remove all consecutive PEM no-start-line errors (not just the last one), wrapped in a `do { ... } while(0)` for safety.
- The `SendTicket` function is simplified to use `SendHandshakeMsg` to support fragmenting the larger ticket.
---
### `src/x509.c`
- `loadX509orX509REQFromPemBio` now accepts `TRUSTED_CERT_TYPE` in addition to `CERT_TYPE` and `CERTREQ_TYPE`.
- **Streaming BIO support**: When `wolfSSL_BIO_get_len()` returns ≤ 0 (e.g., pipes/FIFOs), the function no longer returns an error. Instead, it sets an initial buffer of `MAX_X509_SIZE` and dynamically grows (doubling) up to `MAX_BIO_READ_BUFFER` (`MAX_X509_SIZE * 16`) as data is read byte-by-byte.
- **Alternate footer detection**: For `TRUSTED_CERT_TYPE`, the PEM reader also checks for the regular `CERT_TYPE` footer (`-----END CERTIFICATE-----`) in addition to the trusted cert footer (`-----END TRUSTED CERTIFICATE-----`), so it can parse either format.
- Removed two lines that set `cert->srcIdx` to `SIGALGO_SEQ` offset. This makes `cert->srcIdx` reflect the end of parsed certificate data. This is used by `loadX509orX509REQFromBuffer` to detect where auxiliary trust data begins in trusted certificates.
---
### `src/ssl_sk.c`
- Added a `STACK_TYPE_X509_CRL` case to `wolfssl_sk_dup_data` that calls `wolfSSL_X509_CRL_dup` for deep-copying CRL stack elements. Previously, `STACK_TYPE_X509_CRL` fell through to the unsupported default case.
---
### `wolfssl/openssl/ssl.h`
- `sk_X509_dup` now maps to `wolfSSL_shallow_sk_dup` (was `wolfSSL_sk_dup`/deep copy). This matches OpenSSL's behavior where `sk_X509_dup` does a shallow copy.
- `sk_SSL_CIPHER_dup` similarly changed to `wolfSSL_shallow_sk_dup`.
---
### `src/ssl_api_cert.c`
- When `ssl->ourCert` is `NULL` and the SSL owns its cert, the function now checks if `ssl->ctx->ourCert` points to the same certificate (by comparing DER buffers). If so, it returns the ctx's `X509` pointer directly. This maintains pointer compatibility for applications (like nginx OCSP stapling) that use the `X509*` from `SSL_CTX_use_certificate` as a lookup key.
### `src/bio.c`
- When `wolfssl_file_len` returns `WOLFSSL_BAD_FILETYPE` (now returned for pipes/FIFOs), `wolfSSL_BIO_get_len` treats it as length 0 instead of propagating the error.
---
### `tests/test-maxfrag.conf` and `tests/test-maxfrag-dtls.conf`
- Removed `DHE-RSA-AES256-GCM-SHA384` test entries because the ClientKeyExchange doesn't fit in the selected max fragment length.