d2i_make_pkey() replaces the key data, size and type of a caller-supplied
WOLFSSL_EVP_PKEY, but left pkcs8HeaderSz, mldsaOID, pkey_curve and
save_type describing the key the object held before.
A stale pkcs8HeaderSz is the damaging one. pkcs8_encode() and
wolfssl_i_evp_pkey_get_der() both encode from pkey.ptr + pkcs8HeaderSz,
so after
d2i_PrivateKey(EVP_PKEY_RSA, NULL, &p, pkcs8RsaDer);
d2i_PrivateKey_EVP(&pkey, &q, traditionalEccDer);
PEM_write_bio_PKCS8PrivateKey() reports success while wrapping the ECC
key with its first 26 bytes cut off, and the resulting PEM cannot be
read back.
Only the d2i_PUBKEY and d2i_PrivateKey_EVP routes are affected;
d2i_PrivateKey and d2i_AutoPrivateKey go through d2i_evp_pkey(), which
allocates a fresh object and recomputes the header size.
The same branch also drops the data and the key object of the previous
key without releasing either. pkey.ptr is overwritten with a fresh
allocation, and wolfSSL_EVP_PKEY_free() only disposes of the object
matching the type currently set, so the object of a key whose type has
since changed is never freed. The sequence above leaks the 1219 byte RSA
encoding together with the WOLFSSL_RSA and its bignums, 13 allocations
in all. The data is released after the new encoding has been copied in,
since the caller may be decoding out of it.
Clear the metadata and dispose of the previous key on the reuse branch,
so a reused object decodes to the same state as a new one, and add a
regression test comparing the PKCS#8 output of a reused key against a
freshly decoded one.
This is hardening rather than a fix for a reachable defect. Both branches
below are wrong as written, but no entry point tested reaches them:
wolfSSL_d2i_PrivateKey() and wolfSSL_d2i_PUBKEY() both leave pkey->ecc
populated, so wolfSSL_EVP_PKEY_get1_EC_KEY() always takes its up_ref path.
wolfSSL_EVP_PKEY_get1_EC_KEY() has a branch that builds an EC_KEY when the
pkey does not carry one, caches it on the pkey and returns it. It did that
without taking a second reference and without setting ownEcc, so the
single reference the key was created with was handed to the caller while
the pkey kept an unowned pointer to it. A caller releasing what get1 gave
it, as the contract requires, would leave pkey->ecc dangling. The pkey now
keeps the reference the key was created with and the caller gets one of
its own.
The same branch freed the key when neither DER load succeeded but left
pkey->ecc pointing at it. That pointer is now cleared.
wolfSSL_EVP_PKEY_keygen() set ownEcc on the EC path whether or not it had
created the key, so a key placed on the pkey by something that did not
transfer ownership would gain a second owner. Ownership is now claimed
where the key is created.
Adds test_wolfSSL_EVP_PKEY_get1_EC_KEY_reuse(), which releases the
reference get1 returns and then calls get1 again. It covers the path a
decoded pkey actually takes and pins the reference contract; it passes
with and without the change above, which the comment on the test says
plainly so it is not mistaken for a regression test.
The DH case of wolfSSL_EVP_PKEY_keygen() assigned straight over
pkey->dh:
case WC_EVP_PKEY_DH:
pkey->dh = wolfSSL_DH_new();
A caller supplied EVP_PKEY can already hold a DH object.
wolfSSL_EVP_PKEY_set1_DH() takes a reference and sets ownDh, and
wolfSSL_EVP_PKEY_assign_DH() installs one outright, so keygen on such a
pkey dropped the only pointer the EVP_PKEY had to that object without
releasing its reference, and nothing freed it afterwards.
The case now generates into a temporary and frees the previous key when
the pkey owned it, which is the shape the RSA case in the same switch
uses.
Adds test_wolfSSL_EVP_PKEY_keygen_dh_reuse(), which loads DH parameters,
puts them on an EVP_PKEY with set1_DH so the pkey holds a reference, and
then runs keygen on that same pkey. The leak itself is not asserted by the
test: it needs an allocation tracker, and the smoke-test sanitize-asan job
provides one, since it builds with AddressSanitizer and sets no
ASAN_OPTIONS, so LeakSanitizer runs by default there. What the test does
locally is drive the path and show it stays free of double frees under
AddressSanitizer.
Under WOLFSSL_NO_REALLOC, PopulateRSAEvpPkeyDer() and
ECC_populate_EVP_PKEY() emulate XREALLOC by allocating a buffer sized for
the NEW encoding and then copying pkey_sz bytes, the size of the OLD one,
into it:
derBuf = (byte*)XMALLOC((size_t)derSz, pkey->heap, DYNAMIC_TYPE_DER);
if (derBuf != NULL) {
XMEMCPY(derBuf, pkey->pkey.ptr, (size_t)pkey->pkey_sz);
Whenever the replacement key encodes shorter than the one already on the
EVP_PKEY the copy runs past the end of the new allocation. Putting a
public key on a pkey holding a 2048-bit private key copies 1192 bytes
into a 294 byte buffer.
The copy serves no purpose: both functions fill the new buffer with a
fresh encoding immediately afterwards. It is removed rather than bounded.
ECC_populate_EVP_PKEY() also gains the pkey_sz reset that
PopulateRSAEvpPkeyDer() already has, so a failure between the allocation
and the encoding cannot leave the size describing a buffer that holds no
encoding.
The outgoing buffer is now wiped with ForceZero() before it is
reallocated or freed. On a private key it holds a complete RSA or ECC DER,
so returning it to the allocator intact leaves the key recoverable from
the free pool through a later heap over-read, a core dump or a swap page.
wolfSSL_RSA_To_Der_ex() establishes the same convention two frames away.
wolfSSL_EVP_PKEY_free() gets the same treatment, since it releases that
buffer on every normal teardown, as does the PKCS#8 branch of
PopulateRSAEvpPkeyDer(), which frees the unwrapped PKCS#1 key on its
success path once the wrapped copy has been built.
In ECC_populate_EVP_PKEY() that covers all three sites which release the
previous encoding, the two private-key branches as well as the public
one. clearEVPPkeyKeys() leaves pkey.ptr in place, so a pkey decoded from
a private key still carries that DER when a public-only key replaces it.
The wipe there happens before the allocation, since XREALLOC consumes the
old pointer, and pkey_sz and pkcs8HeaderSz are dropped with the contents
so a failed allocation cannot leave either describing a buffer that no
longer holds an encoding.
Where the allocation of the new buffer fails, pkcs8HeaderSz is cleared
along with pkey_sz for the reason given in the previous commit.
ECC_populate_EVP_PKEY() clears pkcs8HeaderSz when it installs a public
key. A SubjectPublicKeyInfo has no PKCS#8 wrapper, but neither
wolfSSL_EVP_PKEY_set1_EC_KEY() nor clearEVPPkeyKeys() resets the field, so
putting a public key on a pkey decoded from a PKCS#8 EC key left the
export paths starting that many bytes inside the new encoding and
returning it short under a success return.
The traditional private-key branch needs the same reset. It runs whenever
the incoming EC key carries no header size of its own, a generated key for
instance, and writes a bare SEC1 ECPrivateKey. Seeding an EVP_PKEY from
certs/ecc-keyPkcs8.der and then calling wolfSSL_EVP_PKEY_set1_EC_KEY()
with a generated key made wolfSSL_i2d_PrivateKey() return 92 bytes
beginning in the middle of the private scalar rather than the 121 byte
encoding. Every export path is affected, including the PKCS#8 encryption
in wolfSSL_PEM_write_bio_PKCS8PrivateKey(), which encrypts that same
misaligned slice.
Adds test_wolfSSL_EVP_PKEY_set1_shrinking_der(), which replaces the key
on an EVP_PKEY with a public-only one for both RSA and ECC and requires
the stored encoding to shrink. The smoke-test job
opensslextra-norealloc-asan builds exactly this configuration under
AddressSanitizer, which is where the over-copy is caught.
The test gates each algorithm on its own prerequisites rather than on one
shared list. WOLFSSL_KEY_TO_DER is defined by settings.h only when RSA is
enabled, so requiring it for the whole test compiled the ECC half out of
any build without RSA, and that half is the only coverage the ECC
over-copy has. The ECC half is seeded from a PKCS#8 wrapped key so that
pkcs8HeaderSz starts non-zero, and its size assertion is exact rather than
a comparison against the previous size, so an export starting at a stale
header shows up as a mismatch rather than passing.
test_wolfSSL_EVP_PKEY_set1_EC_KEY_no_pkcs8() covers the private-key case.
It compares the encoding exported after the replacement against the one a
pkey that never held a wrapped key produces from the same EC key, so a
carried over header size shows up as a size and content mismatch.
PopulateRSAEvpPkeyDer() installs the newly allocated DER buffer on the
EVP_PKEY before encoding into it, but only updates pkey_sz on the success
path at the end. Every failure return in between left pkey_sz describing
the previous encoding while pkey.ptr pointed at a buffer that holds no
encoding at all and can be smaller than the old one. Callers such as
wolfssl_i_evp_pkey_get_der() copy pkey_sz bytes out of pkey.ptr, so they
would read past the new allocation.
The reachable paths are wc_RsaKeyToDer() or wc_RsaKeyToPublicDer()
failing after their size query succeeded, and, under HAVE_PKCS8, the
PKCS#8 buffer allocation or wc_CreatePKCS8Key() failing.
Reset pkey_sz when the new buffer is installed so a failure return leaves
the pkey describing an empty encoding rather than a stale one.
Verified by fault injection, having wc_RsaKeyToDer() fail whenever asked
to write: wolfSSL_EVP_PKEY_set1_RSA() on a populated EVP_PKEY left
pkey_sz at 1192 before this change and leaves it 0 after.
pkcs8HeaderSz is cleared on the same return. It describes an offset into
the encoding pkey_sz measures, and wolfssl_i_evp_pkey_get_der() already
guards the subtraction of one from the other, but pkcs8_encode() and
pkcs8_encrypt() in src/pk.c do not: with pkey_sz reset and a header size
of 26 left over from a PKCS#8 wrapped predecessor, they would compute a
length of 0 - 26 as a word32.
The RSA branch of wolfSSL_EVP_PKEY_keygen() passed &pkey->pkey.ptr
directly to wolfSSL_i2d_RSAPrivateKey():
pkey->pkey_sz = wolfSSL_i2d_RSAPrivateKey(pkey->rsa,
(unsigned char**)&pkey->pkey.ptr);
Following the i2d convention, wolfSSL_RSA_To_Der_ex() treats a non-NULL
*outBuf as a caller supplied buffer: it encodes into it with no size
check and then advances the pointer past the encoding. When ppkey points
at an EVP_PKEY that already carries a DER encoding, the generated private
key is written into that older, typically smaller allocation and
pkey.ptr is left pointing into the middle of it, which the eventual
XFREE() then trips over. Decoding a 2048-bit public key and calling
keygen on the same EVP_PKEY writes about 1190 bytes into a 294 byte
buffer.
The branch now installs the generated key on the pkey and hands the
encoding to PopulateRSAEvpPkeyDer(), which is what
wolfSSL_EVP_PKEY_set1_RSA() already does and what the sibling EC branch
does through ECC_populate_EVP_PKEY(). That function sizes the encoding
first and allocates its own buffer, so i2d is never shown a populated
pkey, and the three copies of the free, encode and assign sequence become
one. It also allocates against pkey->heap, which is the heap every site
that later releases pkey.ptr passes, while i2d deliberately allocates
with a NULL hint because its result is returned to the user.
The old RSA key is released before the new one is installed, which fixes
the previous unconditional overwrite of pkey->rsa leaking the old object,
and success is no longer reported when the encoding fails.
pkcs8HeaderSz is taken from the newly generated key rather than left as
it was. It describes the DER currently held in pkey.ptr, and
PopulateRSAEvpPkeyDer() adds a PKCS#8 wrapper only when the RSA key
carries a header size. d2i_PrivateKey(), d2i_AutoPrivateKey() and
PEM_read_bio_PrivateKey() set the field to 26 for a wrapped RSA key, and a
pkey obtained that way and then reused for keygen kept the 26 while the
encoding underneath was no longer wrapped. Every export path that trusts
the pair then sliced 26 bytes off the front of the new key:
wolfssl_i_evp_pkey_get_der() behind i2d_PrivateKey(), pkcs8_encode()
behind i2d_PKCS8PrivateKey(), and wolfssl_pkey_encrypt() behind
PEM_write_bio_PrivateKey(), returning a corrupt encoding under a success
return. wolfSSL_EVP_PKEY_set1_RSA() already maintains this field.
Adds test_wolfSSL_EVP_PKEY_keygen_reuse(), which runs keygen on an
EVP_PKEY populated from a public key DER and requires the resulting
encoding to decode back to the generated key, plus a second pass seeded
from a PKCS#8 key since the public key seed leaves pkcs8HeaderSz at zero
and cannot catch the stale header.
The test is gated on OPENSSL_EXTRA rather than OPENSSL_ALL, since nothing
it calls needs the latter, and on !NO_ASN and !NO_PWDBASED because
wolfSSL_i2d_PrivateKey() is compiled only under those. The PKCS#8 pass
additionally needs !NO_CERTS, which is what load_file() is gated on.
The forward declaration is gated on the same condition as the definition
rather than on WOLFSSL_KEY_GEN, which settings.h only happens to derive
WOLFSSL_KEY_TO_DER from today.
The sibling cases in the same switch are left alone deliberately. The DH
case does not free a previous pkey->dh, and the EC case promotes a
borrowed pkey->ecc to owned, both of which are the same ownership class
this change fixes for RSA. They are pre-existing, they need their own
tests, and folding them in here would widen a buffer overrun fix into a
rework of EVP_PKEY_keygen ownership across four algorithms.
SignCert() checked the output buffer with
requestSz + MAX_SEQ_SZ * 2 + sigSz > buffSz
before handing the buffer to AddSignature(). That accounts for the outer
SEQUENCE header but not for the signatureAlgorithm AlgorithmIdentifier
(OID plus optional NULL parameters) or the signatureValue BIT STRING
header that AddSignature() also writes, an under-count of about 13 bytes.
AddSignature() takes no buffer size of its own, so any certificate whose
final encoding lands in that narrow band just under buffSz passed the
check and was written past the end of the buffer.
The same estimate was used in wc_SignCert_cb().
Both call sites now ask AddSignature() for the exact encoding size by
passing a NULL buffer first, then compare that against buffSz. This is
the two-pass idiom already used when signing CRLs in SignCrl(), in
wolfssl_x509_make_der() and in wolfSSL_X509_CRL_sign(). Both the template
and the original ASN.1 encoders support the NULL buffer sizing call.
The comparison is made unsigned, matching the pre-flight in SignCrl().
Casting buffSz to int made a buffer larger than INT_MAX compare negative
and rejected every signature for it.
Both functions also now bound requestSz against buffSz up front.
MakeSignature() and MakeSignatureCb() hash requestSz bytes out of buf
before any size check runs, so a caller passing the two mismatched got an
out of bounds read of up to requestSz - buffSz bytes before the function
returned. Only a negative requestSz was rejected before. Reaching this
needs the application to pass values that disagree, so it is API misuse
rather than attacker controlled input, but the read side now carries the
same guarantee as the write side.
Reachable from the OpenSSL compatibility layer through
wolfSSL_X509_sign() and wolfSSL_X509_REQ_sign(), where the caller
controls the certificate contents that steer the encoded size into the
band.
Adds test_wc_SignCert_buffer_bounds(), which signs into buffers sized
across the band below the exact encoding size and requires BUFFER_E and
an untouched guard region for each, while still accepting the exact size.
test_wc_SignCert_cb() gains the same check for the callback entry point,
using its RSA half where the PKCS#1 v1.5 signature is fixed length, in
both directions so that an over-conservative estimate is caught too.
The bounds test covers ECDSA as well as RSA. IsSigAlgoNoParams() drops
the NULL parameters from the AlgorithmIdentifier, so the width an
estimate under-counts by differs between the two: 24 bytes of wrapper
against the 12 byte estimate for RSA, but only 19 for ECDSA, putting the
capacities that used to be accepted and overrun within 8 bytes of the
exact size.
An ECDSA encoding size cannot be measured once and reused, because the
DER INTEGERs holding r and s change length with the leading zero bytes of
each new signature. The sweep measures a fresh reference size every
iteration and, rather than requiring BUFFER_E for a capacity that the
next signature might genuinely fit, asserts what has to hold either way:
the call returns BUFFER_E or a size within the capacity, and the guard
region past the capacity is untouched. That covers the whole band instead
of trading it away for a margin wide enough to absorb the jitter.
The prerequisites are split into one condition macro per algorithm rather
than one shared list. Gating the whole test on the RSA prerequisites
would have compiled the ECDSA sweep out of a build without RSA, which is
exactly where it is the only coverage that exists.
Both tests set an explicit serial number. wc_InitCert() leaves serialSz at
zero, so wc_MakeCert() generates a random serial, and GenerateInteger()
does not shrink the length after dropping leading zero bytes, which lets
the promoted byte carry the MSB and makes the encoder pad the INTEGER with
an extra 0x00. Measured over 200000 generated bodies, 813 of them, 0.406
percent, came out one byte longer, which would have made the swept
capacities disagree with the reference size for roughly one run in 128.
wc_LmsKey_Reload and wc_XmssKey_Reload return success without doing any
work whenever key->devId != INVALID_DEVID, on the assumption that a
device-bound key has its private state inside that device. That assumption
does not hold for a caller that sets devId only to route primitives to a
hardware accelerator while keeping the key state in its own storage.
A wolfHSM server is exactly that caller. It configures a server-wide devId
so AES, ECC and RSA reach the platform accelerator, then for LMS/XMSS it
installs read/write callbacks and calls Reload to rebuild the expanded
private key before signing. With a hardware devId configured, Reload
returned 0 immediately and the key was left unusable for the first sign
that follows. The failure needs no invalid input, only a server built with
an accelerator.
For LMS that first sign is a crash: key->priv_data stays NULL, wc_hss_sign
finds priv.inited clear and calls wc_hss_init_auth_path, which derives its
first read from a NULL priv pointer. On a target without a mapping at low
addresses that is a bus fault. For XMSS the skipped reload never allocates
key->sk, leaving both the pointer NULL and sk_len 0, so the outcome depends
on the caller's read callback: one that honours the length it is given
returns nothing and the sign fails with IO_FAILED_E, while one that writes
a fixed-size record faults on the NULL destination.
Key generation was unaffected and hid the problem: wc_LmsKey_MakeKey
already treats devId as "offer the operation to the callback, fall back to
software on CRYPTOCB_UNAVAILABLE", so it populates the key correctly when
the accelerator declines. Reload had no equivalent fall-through, so the
same key and the same devId were interpreted two different ways by the
same API.
Qualify the short-circuit with key->read_private_key == NULL. A caller that
has installed a read callback is asking for the software reload to fetch
the state through it, whereas a genuinely device-backed key installs no
such callback. Key generation keeps offering the operation to the crypto
callback, so a port with real stateful-hash-signature hardware is not
prevented from using it.
The reference POSIX wolfHSM server runs with INVALID_DEVID, which is why
this was not caught by existing tests. test_wc_LmsKey_reload_devid and
test_wc_XmssKey_reload_devid cover both arms of the new condition: a key
whose read callback is set must come back from Reload with its private key
expanded (priv_data for LMS, sk for XMSS) and able to sign, while a key on
the same devId with no read callback must still short-circuit. Both tests
register a crypto callback that declines every operation with
CRYPTOCB_UNAVAILABLE, which is the accelerator this fix is about. Asserting
on the expanded key means the old behaviour fails the assertion rather than
the NULL dereference it leads to. The rest of the LMS and XMSS suite is
unaffected, as every other key there uses INVALID_DEVID.
The XMSS test needs the H10 SHA-256 parameter set, which is only in the
algorithm table when both the hash and the height are compiled in, so it
carries a guard for that. The crypto callback's own guard is the exact
union of the two test guards, or a build with only one of the two
algorithms would emit it with no caller and fail -Werror.
Both the Reload implementation comments and the published Doxygen now state
that the read callback, not the devId, decides whether the software reload
runs.
wolfcrypt/src/wc_mldsa.c: WC_C_DYNAMIC_FALLBACK fixes for AVX512.
tests/api/test_frodokem.c, wolfcrypt/test/test.c: fixes for WC_DEBUG_CIPHER_LIFECYCLE.
wolfcrypt/src/fe_x25519_asm.S, wolfcrypt/src/port/arm/armv8-32-aes-asm.S,
wolfcrypt/src/port/arm/armv8-aes-asm.S, wolfcrypt/src/port/arm/armv8-aes-asm_c.c,
wolfcrypt/src/port/arm/thumb2-aes-asm.S, wolfcrypt/src/sha3_asm.S,
wolfcrypt/src/wc_mldsa_asm.S: regenerate from scripts#647
.github/workflows/fips-dev-no-post.yml:
* update "minutes" for tests using empirical data;
* add --enable-experimental --enable-all-quantum-crypto to kernel-settings-all-asm scenario and rename it kernel-settings-all-pqc-asm; add all-pqc-asm-fallback-fuzzer scenario.
wolfcrypt/src/wolfentropy.c: remove WC_FIPS_LL_CRYPTO (it is not a FIPS file except in FIPS v5.2.4).
wolfcrypt/src/sha256.c: fix for rebase error (stray #endif).
to avoid inadvertent configuration shifts -- HAVE___UINT128_T is a backend
selector (SP_WORD_SIZE, CURVED25519_128BIT, CURVED448_128BIT), not merely a
type-availability macro;
wolfcrypt/src/falcon.c: accept either defined(__SIZEOF_INT128__) or
defined(HAVE___UINT128_T) in FALCON_MULHI() implementation selector.
wolfssl/wolfcrypt/sp_int.h: fix size of struct sp_ecc_ctx when
SP_WORD_SIZE == 64 (as when HAVE___UINT128_T is defined), fixing assert
failure in sp_c64.c sp_ecc_verify_256_nb(). The P-256-only bucket was sized
against the C32 layout (verify ctx 2376 <= 2560); the C64 ctx is 2640. The
384 and 521 buckets are equally word-size-blind but currently pass at 64 bits
on margin (3600 <= 3840, 4560 <= 5280).
.github/workflows/fips-dev-no-post.yml: add reporting of fuzzing seed.
wolfcrypt/src/aes.c, tests/api/test_aes.c, .wolfssl_known_macro_extras:
* change FIPS AES-GCM nonce size restrictions from from opt-out (WC_FIPS_AESGCM_ALLOW_SHORT_NONCES) to opt-in (WC_FIPS_AESGCM_NO_SHORT_NONCES).
* apply restrictions only on encryption operations, never on decryption.
configure.ac: fix HAVE_FIPS_VERSION of fips-ready; fix enable_dh setup in KERNEL_MODE_DEFAULTS setup; fix help message for --enable-dh.
linuxkm/x86_vector_register_glue.c: add dump_stack() on each BUG/WARNING message that didn't already have it.
src/tls.c: fix a couple leaks in TLSX_KeyShare_GenDhKey().
tests/swdev/swdev.c: gate src->sha_method access in swdev_sha256_copy_state() and swdev_sha512_copy_state() appropriately.
tests/unit.c: conditionally include dh.h, to assure wc_dh_enable() is available.
wolfcrypt/src/dh.c:
* in wc_InitDhKey_ex(), zero the key at entry unless null, remove duplicate key->trustedGroup = 0, and call wc_FreeDhKey() on error at end.
* add missing wc_dh_enabled checks in wc_DhGeneratePublic() and wc_DhGenerateParams().
wolfcrypt/src/error.c: fix missing space in FIPS_UNAPPROVED_E string.
b/wolfssl/wolfcrypt/settings.h: sense __SIZEOF_INT128__ and if defined, but HAVE___INT128_T and/or HAVE___UINT128_T are undefined, define them.
wolfcrypt/src/falcon.c: tweak the gate on __uint128_t availability to lean solely on HAVE___UINT128_T.
wolfcrypt/src/random.c: fix a couple missed WC_NO_ERR_TRACE() wrappers.
wolfcrypt/src/rng_bank.c: properly tolerate WC_ACCEL_INHIBIT_E as a retval from bank->affinity_lock_cb().
wolfcrypt/src/sha256.c, wolfcrypt/src/sha512.c: move #undef WC_C_DYNAMIC_FALLBACK for WOLFSSL_AESNI without USE_INTEL_SPEEDUP to follow all includes, assuring no struct layout conflict.
wolfcrypt/src/sha512.c: fix wrong call in intelasm Transform_Sha512() !WC_C_DYNAMIC_FALLBACK SHA512_C path.
wolfcrypt/test/test.c: fix double-WC_TEST_RET_ENC_EC() in mldsa_param_*_vfy_test().
Under WC_C_DYNAMIC_FALLBACK, SAVE_VECTOR_REGISTERS2() can fail on any call, so
two calls on the same object can dispatch differently. Each of these
algorithms had state that silently assumed a single dispatch for its lifetime.
wolfcrypt/src/wc_mldsa.c: add MLDSA_NTT_AVX2()/MLDSA_INVNTT_AVX2() selecting
the "full" AVX2 NTT/invNTT under WC_C_DYNAMIC_FALLBACK. The non-full variants
leave NTT-domain coefficients in a permuted, lane-interleaved order that only
their matching consumers understand, whereas the full variants and the C
implementations use standard order. NTT-domain data at rest (cached s1/s2/t0
vectors, the challenge polynomial) can be produced and consumed by
differently-dispatched calls, so its representation must be dispatch-invariant.
Without fallback, dispatch is invariant and the ~2%/~4% faster permuted-order
variants are kept. Both pipelines are bit-identical end to end.
wolfcrypt/src/wc_mlkem_poly.c: in mlkem_derive_secret(), re-initialize the
shared SHAKE-256 object under WC_C_DYNAMIC_FALLBACK. The buffer-stuffing
shortcut assumes a freshly initialized (zeroed) sponge, which no longer holds
once the C fallback legs of mlkem_gen_matrix()/mlkem_get_noise() drive the XOF
on that object and leave it mid-squeeze.
wolfcrypt/src/wc_slhdsa.c: in slhdsakey_fors_sign(), replace the
CAN_SAVE_VECTOR_REGISTERS() test with an actual SAVE_VECTOR_REGISTERS2() == 0
acquisition and a matching RESTORE_VECTOR_REGISTERS(), so the region is held
rather than merely predicted to be available.
wolfcrypt/src/wc_frodokem_mat.c: in the AES row kernels of
frodokem_mul_add_as_plus_e_aes() and frodokem_mul_add_sa_plus_e_aes(), re-key
with wc_AesSetKeyDirect() when IS_INTEL_AESNI() but !aes->use_aesni. The
kernels consume aes->key directly, which holds an AES-NI-layout schedule only
if SetKey ran with vector registers available; under fallback a failed
SAVE_VECTOR_REGISTERS2() inside SetKey returns success having keyed only the
C-fallback schedule. Re-keying happens inside the held region, where the
nested save always succeeds. Loop conditions gain (ret == 0) so a re-key
failure stops the run.
wolfssl/wolfcrypt/settings.h: with the above, ML-KEM, ML-DSA, SLH-DSA and
FrodoKEM are fuzzer-clean, so the DEBUG_VECTOR_REGISTER_ACCESS_FUZZING
exclusion narrows from the _WC_BUILDING_WC_MLKEM_POLY_C / _WC_BUILDING_WC_MLDSA_C
/ _WC_BUILDING_WC_SLHDSA_C set to _WC_BUILDING_FALCON_C alone. Falcon stays
excluded because it uses FP or vector registers in all of its asm
implementations and there is no option yet to build the C-no-FP implementation
alongside them.
tests/api/test_mldsa.c: in test_mldsa_encode_w1_large_values(), pin dispatch to
the C path with WC_DEBUG_SET_VECTOR_REGISTERS_RETVAL() for the duration of the
test and restore it afterward. The two calls being compared are only specified
-- and only equal -- on the valid input domain, so letting the fuzzer send them
down different (AVX2 vs C) implementations is not a meaningful comparison.
The AVX2 constant-time table-lookup routines seed a broadcast vector with a
legacy-SSE GPR->XMM move (movd/movq), which writes bits [127:0] and leaves
[255:128] UNMODIFIED, then read the register at full YMM width via
vpermd %ymm,%ymm(zeroed),%ymm (a lane-0 broadcast across all 256 bits). If a
prior vector op left the upper lane non-zero, the broadcast is corrupt and the
constant-time selection returns the wrong table entry -- a wrong ECC point/entry
in sp_{256,384,521}_get_{point_33,entry_64,entry_65}_avx2 and
sp_{2048,3072,4096}_get_from_table_avx2, or a wrong X25519 public key from
fe_cmov_table_avx2. Deterministic given register history; surfaces as
intermittent failures because it depends on the upper lane being dirty on entry.
Under kernel_fpu_begin (which does not zero YMM) a dirty upper lane is ambient,
which is why ED25519 asm was kept disabled in kernel mode. Fix: emit the VEX
form (vmovd for 32-bit source, vmovq for 64-bit), which zeroes [255:128].
* Use defined(WC_HAVE_RNG_BANKREF), not defined(WC_RNG_BANK_SUPPORT), as the feature sensor for RNG bankrefs.
* Add DRBG_KAT_FIPS_E and DRBG_CONT_FIPS_E to the list of immediate-failure errors in wc_rng_bank_init().
WOLFSSL_DEBUG_TRACE_ERROR_CODES support for internal DRBG errors.
Converts the DRBG internal status #defines (DRBG_SUCCESS/DRBG_FAILURE,
WC_DRBG_*) to enums, that are wrapped in WC_ERR_TRACE() when
WOLFSSL_DEBUG_TRACE_ERROR_CODES.
Deploys well-known error codes and WC_NO_ERR_TRACE() as needed throughout.
Refactor WC_C_DYNAMIC_FALLBACK architecture to allow per-call alternation
between asm and C:
* Under WC_C_DYNAMIC_FALLBACK keep the block buffer as the raw big-endian stream
and byte-reverse just-in-time inside the C transform, so a given wc_Sha256 /
wc_Sha512 instance may switch between the vectorized and pure-C transforms per
call without producing a wrong digest.
* Add Transform_*_C_from_raw / Transform_*_Len_C_from_raw JIT-reversing
adapters; the dispatchers early-out through them on (method == C) ||
SAVE_VECTOR_REGISTERS2 failure; caller-side method-keyed ByteReverse sites are
compiled out under the raw-buffer convention and the final block's length words
are written unconditionally big-endian.
* Remove the init-time CAN_SAVE_VECTOR_REGISTERS pin from Sha*_SetTransform so
the recorded method reflects pure CPU capability (enabling fall-forward, not
only fallback). Update the bulk paths to check the transform return and not
advance on failure.
* The raw-buffer convention is scoped to WC_C_DYNAMIC_FALLBACK specifically --
not to WC_NO_INTERNAL_FUNCTION_POINTERS -- because only the fallback build can
change transform mid-object; a plain no-function-pointers build picks one method
and keeps it, so it retains the conventional host-endian buffer (no change from
incumbent code).
* Drop the per-object `.sha_method` member. Method selection is a property of
the CPU, not of the hash object, so it becomes a file-scope static in each .c,
set once (Sha*_SetTransform() early- returns when already set) and read by every
instance. Shrinks both structs.
* When WOLFSSL_AESNI is enabled without the rest of USE_INTEL_SPEEDUP, `#undef
WC_C_DYNAMIC_FALLBACK` -- AES-NI alone satisfies WC_HAVE_VECTOR_SPEEDUPS but
leaves SHA with no vectorized transform to fall back from, and the caller-side
gating would otherwise suppress a byte-reversal that is still required.
wolfcrypt/src/rsa.c: wrap wc_hash2mgf in a targeted -Wswitch-enum pragma
configure.ac: rename $ENABLE_ORIGINAL -> $ENABLE_ORIGINAL_KYBER to disambiguate.
wolfssl/wolfcrypt/types.h: tighten a braced-group guard with __STRICT_ANSI__
(pedantic-mode correctness).
wolfssl/internal.h: add the WOLFSSL_API_PREFIX_MAP mapping for TLSX_CKS_Parse.
`.hidden name` macro under __ELF__, currently empty under !__ELF__.
wolfcrypt/src/*.S, wolfcrypt/src/port/{arm,ppc32,ppc64,riscv64}/*.S: emit
WC_ASM_ATT_HIDDEN for internal ATT-syntax symbols so they don't become
dynamic-table entries or otherwise pollute symbol namespace beyond the
library/module.
(unversioned defined(HAVE_FIPS), not a version arm -- hmac.c is in-boundary and
master's copy only compiles at v7+/MAJOR=8). This structurally closes the
old-TLS MD5 PRF: wc_PRF_TLSv1 -> wc_PRF(md5_mac) -> wc_HmacSetKey(WC_MD5) ->
BAD_FUNC_ARG. Separately, in wc_HKDF_Expand_ex, add `else if (ret == 0) return
BAD_FUNC_ARG;` after the wc_HmacSizeByType call: the existing code guarded
ret < 0 but not ret == 0, and hashSz is the divisor in the
`outSz/hashSz + ((outSz % hashSz) != 0) > 255` check three lines below.
wolfcrypt/src/kdf.c: delete the two WC_HASH_TYPE_MD5_SHA guards in wc_PRF /
wc_PRF_TLS -- they were a domain error (that arg is wc_MACAlgorithm, where
WC_HASH_TYPE_MD5_SHA == 9 == sm3_mac, so the guard blocked SM3, not MD5-SHA),
and the hmac.c reject is the correct layer.
wolfcrypt/src/evp.c: drop the MD5 EVP mapping at FIPS >= 5 (evp.c is out of
boundary, so the version arm is live and correct here).
tests/api/test_kdf.c: derive secLen from MAX_PRF_HALF rather than hardcoding
521/261 -- MAX_PRF_HALF is config-dependent (516 under HAVE_FFDHE_8192, 388
under FFDHE_6144, else 260), so the hardcoded value made the BUFFER_E
expectation config-dependent.
wolfcrypt/src/cmac.c: on _InitCmac_common failure free the Aes and set
cmac->type = WC_CMAC_NONE, so a contract-violating wc_CmacFree on a
never-initialized object hits a no-op arm instead of re-entering wc_AesFree;
add explicit case WC_CMAC_NONE arms to the three type switches
(wc_CmacUpdate, wc_CmacFree, wc_CmacFinalNoFree).
FIPS_BAD_VALUE_E in the GCM IV-construction paths under HAVE_FIPS, overridable
by WC_FIPS_AESGCM_ALLOW_SHORT_NONCES. Decrypt accepts any supported length per
SP 800-38D (IV construction requirements bind encryption only).
The floor takes two shapes, both correct by construction:
- wc_AesGcmInit_local (reached from wc_AesGcmInit and
wc_AesGcmEncryptInit_ex, which pass a decrypt_p flag):
(ret == 0) && (! decrypt_p) && (ivSz > 0) && (ivSz < GCM_NONCE_MID_SZ)
The `ivSz > 0` clause is load-bearing -- iv is an optional argument there,
and the key-only re-init form (iv == NULL, ivSz == 0) used for
module-generated-IV streaming must still pass.
- wc_AesGcmSetIV / wc_AesGcmSetExtIV: bare `ivSz < GCM_NONCE_MID_SZ`, no
ivSz>0 clause needed -- CheckAesGcmIvSize() already admits only {8,12,16},
so ivSz == 0 cannot reach the floor.
tests/api/test_aes.c: update tests for new FIPS nonce size restrictions.
.wolfssl_known_macro_extras: add WC_FIPS_AESGCM_ALLOW_SHORT_NONCES.
is_enabled, WC_DH_INITIAL_RUNTIME_ENABLEMENT, WC_DH_HAVE_RUNTIME_ENABLEMENT.
Place the enablement check AFTER key->heap/trustedGroup init in the five entry
points (wc_InitDhKey_ex, wc_DhGenerateKeyPair, wc_DhAgree, wc_DhAgree_ct,
_DhSetKey) so a disabled-DH early return never leaves a half-initialized key for
wc_FreeDhKey to mp_clear on garbage.
configure.ac: add --enable-dh=conditional; when DH is enabled (directly or via
all-crypto) set it initially usable under FIPS v7 with
-DWC_DH_INITIAL_RUNTIME_ENABLEMENT=1; remove the FIPS-v7 DH force-off (in FIPS
v7+, disable build by default, unless building in kernel mode with DH
registration enabled).
linuxkm/lkcapi_glue.c: bracket LKCAPI registration with
`need_dh_disable = (wc_dh_enable() == 0)` ... `if (need_dh_disable)
wc_dh_disable();`, so DH is disabled on every exit path, and only by the caller
that actually enabled it (wc_dh_enable returns ALREADY_E if DH was already on,
so this never disables a DH some other context legitimately enabled).
tests/unit.c, wolfcrypt/test/test.c: bracket the DH tests with enable/disable so
they succeed regardless of runtime initial default enablement.
and AM_CONDITIONAL BUILD_FIPS_NO_POST. Refactor FIPS dev/ready version setup:
hoist ENABLED_FIPS_DEV / ENABLED_FIPS_READY to set -DWOLFSSL_FIPS_DEV /
-DWOLFSSL_FIPS_READY centrally, and switch the FIPS AS_CASE arms from
`test "$FIPS_VERSION" != "dev"` to `test "$ENABLED_FIPS_DEV" != "yes"` so the
dev semantics extend to v5-dev/v6-dev/lean-aesgcm-dev.
src/include.am: under !BUILD_FIPS_NO_POST, drop fips.c / fips_test.c /
wolfcrypt_first.c / wolfcrypt_last.c from the build (dev-no-post uses no fips
repo content).
wolfssl/wolfcrypt/settings.h, wolfssl/wolfcrypt/wc_compat.h: under
WOLFSSL_FIPS_DEV_NO_POST, squat WOLF_CRYPT_FIPS_H to inhibit fips.h, and change
the FIPS_READY/DEV version block guard to `!defined(HAVE_FIPS_VERSION)`
(required so an externally supplied version is not clobbered).
wolfssl/wolfcrypt/fips_test.h: add WOLFSSL_FIPS_DEV_NO_POST stub block
(fipsCastStatus_get, the PRIVATE_KEY macros) so master builds without the fips
repo.
linuxkm/linuxkm_wc_port.h, linuxkm/module_hooks.c: accommodate
WOLFSSL_FIPS_DEV_NO_POST (guard verifyCore / CAST / fencepost paths that the
fips repo would otherwise provide; force WC_USE_PIE_FENCEPOSTS_FOR_FIPS).
wolfcrypt/test/test.c: in hmac_sha256_test(), don't expect HMAC_KAT_FIPS_E in
WOLFSSL_FIPS_DEV_NO_POST builds.
wolfssl/wolfcrypt/types.h: add stub macro for
wolfCrypt_SetPrivateKeyReadEnable_fips() when WOLFSSL_FIPS_DEV_NO_POST.
including libwolfssl_sources.h, replacing the previous per-file `#define
FIPS_NO_WRAPPERS` that was placed *after* the include and therefore never took
effect. Low-level crypto TUs stop carrying a per-file opinion about wrapper
generation; settings.h now derives FIPS_NO_WRAPPERS centrally. des3.c and
wolfentropy.c newly acquire FIPS_NO_WRAPPERS (they had none). port/st/stm32.c
converts to the same idiom (drops its HAVE_CONFIG_H/config.h + redundant types.h
block).
Note on scope: FIPS_NO_WRAPPERS governs how the *including* TU resolves its own
outbound calls -- it does not change what the file makes available to others,
beyond defining the FIPS-supported APIs without the `_fips()` extension the
wrappers arrange. The pre-existing effect of the misplacement was that
boundary-internal calls resolved to the wrapped forms, i.e. took an unintended
round-trip back out through the wrappers.
wolfssl/wolfcrypt/settings.h: derive FIPS_NO_WRAPPERS from
(WC_FIPS_LL_CRYPTO || WOLFSSL_FIPS_DEV_NO_POST) under HAVE_FIPS, positioned
after the config-source selection so it sees HAVE_FIPS regardless of whether it
arrived via command line or user_settings.h.
already set, so settings.h can distinguish assembly translation units.
wolfssl/wolfcrypt/settings.h: in the config-source selection, when
BUILDING_WOLFSSL_ASM && WOLFSSL_USER_SETTINGS_ASM, include user_settings_asm.h
(the assembly-safe, directives-only header produced by user_settings_asm.sh)
instead of user_settings.h, which may contain C that breaks the assembler.
wolfcrypt/src/aes_asm.S: drop the file's bespoke copy of the user_settings_asm.h
selection block and route through libwolfssl_sources_asm.h, so the choice lives
in one place. (aes_asm.S is the only .S that open-coded this.) Also add `#define
WC_FIPS_LL_CRYPTO` immediately above the new include.
"Supplied value was rejected by FIPS policy" and FIPS_UNAPPROVED_E "Requested
operation succeeded, but supplied parameters are unapproved for FIPS". The
first is a new fatal error, the second is a new nonfatal error to which
WC_FIPS_NOT_APPROVED will be bound.
Second review follow-up on the async record layer series.
Stop the probe reselecting the cipher side. BuildMessage()'s BUILD_MSG_BEGIN
case can call SetKeysSide() for DTLS with secure renegotiation, which swaps
the active encryption state and clears recordSzOverhead. That is not part of a
size calculation, and after the previous commit the suspended build survives
to resume against whatever side the probe last chose, so a DTLS 1.2 record
suspended for PREV_ORDER could resume against the renegotiation keys. Skip it
when sizeOnly is set; the sizes are the same either way.
The probe itself has to keep running. Not re-entering BuildMessage at all
while a build is suspended looks tidier, but wolfssl_local_GetMaxPlaintextSize()
derives the DTLS fragment size from this result, so falling back to the upper
bound there shrinks fragments inconsistently between calls and the MTU
reproducer fails its buffer comparison. Saving and restoring the two fields is
what keeps the answer exact.
Resume inside the record when handshake content is left. The previous commit
declined to skip the padding for a fragmented or coalesced
certificate_request, which was right, but left processReply at doProcessInit
with the index inside the record, so the resume still started a fresh record
parse in the middle of one. Mirror both halves of the end of record block
instead: set runProcessingOneMessage when content remains, advance past the
padding only at the boundary.
Note the shared state at the source. BuildMessage() and BuildTls13Message()
write ssl->options.buildMsgState even for a sizeOnly probe with asyncOkay
clear, where everything else goes to the caller's own arguments. Nothing said
so at those sites, so the next sizeOnly caller would reintroduce this.
Record why only one of the three wc_ecc_make_key_ex() calls in eccsi.c needs
a wait: the other two are preceded by wc_ecc_free(), which clears the marker
their pending path is gated on. Moving either free would make them pend.
Test changes. Force the overhead cache cold before probing, otherwise an AEAD
suite answers from the cache without ever calling BuildMessage and the
assertions hold no matter what the probe did. Compare against BuildMessage's
own figure rather than only checking the size is positive, and run the whole
thing for TLS 1.3 as well as TLS 1.2, since BuildTls13Message() clobbers the
state by a different route: its sizeOnly return bypasses exit_buildmsg
entirely. Checked by stubbing the restore out again, which fails the test.
Also spell the new guard in cryptocb_test() as #if defined(WOLFSSL_ASYNC_CRYPT)
to match the rest of that file, which uses that form 170 times against 4.
cryptocb_test() generates a key with wc_ecc_make_key() and assigns the
result straight to ret. In an async build that call returns WC_PENDING_E,
which is not an encoded test result, so the raw -108 propagated out of
the test and printed as "error L=108" with no error code at all.
The key is reached through myCryptoDevCb, which services EC key
generation by calling wc_ecc_make_key_ex() on the same key after setting
key->devId = INVALID_DEVID. That comment says the intent is to force
software, and it does stop the crypto callback from dispatching again,
but the pending path in _ecc_make_key_ex() is gated on asyncDev.marker
rather than devId. The marker is untouched, so the inner call still goes
pending and the callback hands WC_PENDING_E back to its caller.
Wait at the call site rather than in the callback. Every other key
generation in this file already does exactly that, a callback returning
WC_PENDING_E is legitimate for a real asynchronous device, and the same
devId idiom appears 48 times in myCryptoDevCb against 48 different keys,
so there is no single place in the callback to fix.
With this, testwolfcrypt passes in full under --enable-all with
--enable-asynccrypt-sw, where it previously stopped here. Verified
against plain --enable-all as well, which is unaffected: the addition
compiles out entirely without WOLFSSL_ASYNC_CRYPT.
That configuration still cannot complete make check. unit.test fails in
the cipher suite runner on TLS 1.3 post-handshake authentication, which
is a record layer problem in the library rather than a test defect and
is not addressed here. All 2111 API tests pass.