1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00

6418 Commits

Author SHA1 Message Date
philljj
354598aa05 Merge pull request #11842 from stenslae/fix/pqc-cryptocb-verify-res
Report PQC verify as invalid whenever it returns an error
2026-10-23 18:11:17 -05:00
Tobias Frauenschläger
8ebafb02bd Let an LMS/XMSS key named by id verify on a device without Reload
wc_LmsKey_Verify and wc_XmssKey_Verify accepted only a key in the OK or
VERIFYONLY state before calling the device. A key from InitId or
InitLabel with its parameters set is in PARMSET, and only Reload moves
it on. A verify-only build compiles Reload out, so such a key could
never verify, on any device.

Accept PARMSET before the device call for a key named by id, label or
device context. wolfHSM names a key cached on the HSM only by devCtx,
with no id. Any other key in PARMSET has no public key anywhere and
still gets BAD_STATE_E without reaching the device. Software still
refuses an id key after the device declines, because a key in PARMSET
never has a public key and the existing pubSet check returns
BAD_STATE_E.

test_wc_LmsKey_id_verify and test_wc_XmssKey_id_verify verify with such
a key through a device, by id and by device context. They check that a
key with neither gets BAD_STATE_E without the device being called, and
that the id key fails without a device: BAD_STATE_E, or NO_VALID_DEVID
in a callback-only build with no software verify. Both run in
verify-only builds, so the guard of the verify callback they share now
covers those builds.
2026-10-23 15:23:36 -07:00
Tobias Frauenschläger
007ee17cdf Take the RSA modulus size from the device for a key named by id
wc_InitRsaKey_Id and wc_InitRsaKey_Label name a key held by a crypto
callback device, so the modulus n stays zero in software. Padding read
the key size from mp_count_bits(&key->n) and so got 0 bits:

- WC_RSA_NO_PAD encrypt returned RSA_PAD_E before the device was called.
- WC_RSA_NO_PAD decrypt and verify returned RSA_PAD_E after the device
  had done the operation.
- PSS with a 1024-bit key and SHA-512 picked the wrong default salt
  length, so signing and wc_RsaPSS_VerifyCheck returned PSS_SALTLEN_E.

Add RsaModulusBits, which falls back to wc_RsaEncryptSize when n is not
set. That already asks the device with WC_PK_TYPE_RSA_GET_SIZE, and
covers other keys held in hardware the same way. Use it wherever
padding, unpadding and the PSS salt length need the key size.

test_wc_RsaId_CryptoCb runs these operations with a key from
wc_InitRsaKey_Id against a device that holds the software key. The PSS
half needs only PSS, SHA-512 and the 1024-bit client key, read from
certs/1024 or taken from certs_test.h with USE_CERT_BUFFERS_1024, so it
also runs in builds without WC_RSA_NO_PADDING, such as the cryptocb-only
CI legs. The device does the raw operation in software, so the test
needs software RSA, or swdev in a build with WOLF_CRYPTO_CB_ONLY_RSA.
2026-10-23 15:23:36 -07:00
Tobias Frauenschläger
4a4253682a Keep an HMAC key named by id on its device
wc_HmacInit_Id and wc_HmacInit_Label name a key held by a crypto
callback device, and the caller keys the Hmac with wc_HmacSetKey(NULL,
0). Two things lost that key.

Keying the Hmac again, for a new MAC or another hash, frees it first,
and wc_HmacFree zeroes the whole struct. Only devId was put back, so the
id and label were gone and the device got a key with no name and no
bytes. Restore the id and label as well, as devId is.

When the device declined an update or final, the software path ran
with the empty key from wc_HmacSetKey(NULL, 0) and returned a MAC under
it. Return MISSING_KEY instead when the Hmac is named by id or label and
has no key bytes, as AES does for a key held on a device. An Hmac named
by id that was given key bytes still uses them. A build with PK
callbacks but no crypto callbacks also has wc_HmacInit_Id, but no device
to hold the key and no keyRaw to tell, so the check is left out there.
The check uses keyRaw, which is NULL only after wc_HmacSetKey(NULL, 0).
keyLen keeps only the low 16 bits of the length, so it reads 0 for a
key of 64 KiB.

PKCS#11 declined every such key on a token that reports a minimum HMAC
key size, because it checked hmac->keyLen, which is 0 when the key is
on the token. Check the length only for a key passed in from software;
C_SignInit checks the token key.

test_wc_HmacId_CryptoCb keys an Hmac from wc_HmacInit_Id twice and
checks that the device still sees the id, then that a declined update
and final return MISSING_KEY. It also checks that a label survives a
second wc_HmacSetKey. test_wc_HmacId_NoDevice checks that an Hmac named
by id MACs with key bytes it is given, in builds with or without crypto
callbacks, also with a 64 KiB key, and that one without key bytes gets
MISSING_KEY when no device takes it.
2026-10-23 15:23:36 -07:00
Tobias Frauenschläger
31ecbbbbf7 Report no signatures left for an LMS/XMSS key held by a device
When the device declines a SigsLeft query, wc_LmsKey_SigsLeft and
wc_XmssKey_SigsLeft fall back to software. That is right for a key whose
state software holds, such as one made or reloaded through a devId that
only routes other algorithms. But a key whose state lives on the device,
named by id or reloaded without a read callback, has none in software.
XMSS then called the unset read_private_key and crashed. LMS counted
from the all-zero priv_raw and reported a signature left.

Fall back to software only when it holds the private key: privSet for
LMS, which only a software MakeKey or Reload sets, and sk plus the read
callback for XMSS. Otherwise report no signatures left, as before
d56831c90d. The doc comments list this as a reason for returning 0.

wc_LmsKey_Sign and wc_XmssKey_Sign had the same fallback. When the
device declined to sign with such a key, software signed with the unset
priv_data or sk and crashed. A key generated on the device has no sk
either, so XMSS crashed there even with read and write callbacks set.
Return BAD_STATE_E when the LMS privSet or the XMSS sk is not set.

wc_LmsKey_MakeKey and wc_LmsKey_Reload left priv_raw and priv_data in
place when generating or reading the key, or a later step such as
writing out a new key, failed, so a key that was never saved or loaded
still counted from priv_raw. On any failure after the state and callback
checks, wipe priv_data, priv_raw and the expanded key state and clear
privSet, which is now set as soon as priv_raw holds the key. The
priv_data buffer is kept rather than freed, so a caller that supplied
its own, as WOLFSSL_NO_MALLOC builds must, can retry. A call refused by
those checks leaves a loaded key alone.

test_wc_LmsKey_id_sigsleft and test_wc_XmssKey_id_sigsleft query a key
from InitId on a device that declines, and the LMS test also signs with
it. test_wc_XmssKey_keygen_sign_declined signs with a key generated on
a device that then declines. test_wc_LmsKey_reload_fail_sigsleft and
test_wc_LmsKey_makekey_fail_sigsleft query a key whose Reload failed to
read or whose MakeKey failed to write, and check that a priv_data buffer
the caller supplied is still in place. The reload_devid tests now also
check that a software-held key on a declining devId still reports
signatures left.
2026-10-23 15:23:36 -07:00
Tobias Frauenschläger
2a37b112b5 Let an ML-KEM key named by id encapsulate on a device
wc_MlKemKey_Init_Id and wc_MlKemKey_Init_Label name a key held by a
crypto callback device. They set no key flags, because the key material
stays on the device. But wc_MlKemKey_Encapsulate returned BAD_STATE_E
for a key without MLKEM_FLAG_PUB_SET before calling the device, so a key
named by id could never encapsulate, on any device. A PKCS#11 token key
reloaded by CKA_ID never reached Pkcs11MlKemEncapsulate, which would
find the token public object by id or label.

Skip that early check for a key named by id, label or device context,
and check MLKEM_FLAG_PUB_SET again after the device call, as
wc_MlKemKey_Decapsulate does for MLKEM_FLAG_PRIV_SET since 55084c9a50.
The device context counts because wolfHSM names a key cached on the HSM
only by devCtx, with no id. A device can then encapsulate with a key it
holds. When it declines, or there is no device, a key without a public
key still gets BAD_STATE_E before software draws randomness or reads
any key material. wc_MlKemKey_EncapsulateWithRandom does not call the
device, so its own check stays where it is.

Any other key without a public key must still fail before the device is
called. With no id, label or device context, Pkcs11MlKemEncapsulate
falls back to the first ML-KEM public key on the token, so a caller that
never loaded the peer's key would get a shared secret for the wrong
recipient. Decapsulation is left as 55084c9a50 made it: a key bound to
a device only by its devId still reaches the device, where the token's
own private key is the one the caller means.

test_wc_mlkem_encapsulate_id_key encapsulates with a key from
wc_MlKemKey_Init_Id through a callback device. It checks that the
device is called, that BAD_STATE_E comes back when the device declines,
that a key from wc_MlKemKey_Init without key material never reaches the
device, and that the same key does once it has a device context.
test_wc_mlkem_decapsulate_id_key now also checks that a key bound to
the device only by its devId reaches it.
2026-10-23 15:23:36 -07:00
philljj
de9a72dbda Merge pull request #11864 from Arpan0995/lms-reduced-hash-tests
LMS: fix the no-sha256-256 test failure and the lms=shake256 build without SHA-3
2026-10-23 15:08:09 -05:00
philljj
3803bdd531 Merge pull request #11666 from Frauschi/cryptocb_setkey_keyinstalled
Fix AES and GCM state after a crypto callback claims the key
2026-10-23 13:59:28 -05:00
philljj
cfd96ab778 Merge pull request #11847 from stenslae/fix-dual-alg-test-flake
Tests: Pin validity dates in test_dual_alg_ecdsa_mldsa
2026-10-23 13:28:02 -05:00
philljj
037125ad1e Merge pull request #11708 from padelsbach/shutdown-loop
Fail wolfSSL_shutdown when the peer's record cannot be processed
2026-10-23 11:24:09 -05:00
Tobias Frauenschläger
edb6f7156b Honor WOLF_CRYPTO_CB_FIND in the ARM32 and PPC asm wc_AesSetKey
The ARM32 and PPC asm wc_AesSetKey only ran the crypto callback block
when aes->devId was set. With WOLF_CRYPTO_CB_FIND a find callback can
map an INVALID_DEVID object onto a device, and the AES encrypt paths
already dispatch such objects to it, but the key was never offered to
a SETKEY device or copied into devKey. The device then ran on a key it
never saw.

Skip the devId check under WOLF_CRYPTO_CB_FIND, as the generic
wc_AesSetKey already does. test_wc_CryptoCb_Aes_SetKey_Find maps
INVALID_DEVID onto a key-owning device and checks that wc_AesSetKey and
wc_AesSetKeyDirect both offer it the key.
2026-10-23 08:56:15 +02:00
Tobias Frauenschläger
198602c406 Route the ARM32 asm wc_AesSetKeyDirect through wc_AesSetKey
With WOLFSSL_ARMASM on 32-bit ARM, wc_AesSetKeyDirect() called the
static AesSetKey() directly, so neither the AES_SETKEY nor the SETKEY
device hook saw a re-key, and the plain crypto callback path kept the
old key in devKey. A device that had claimed the previous key went on
encrypting with it while every call returned 0.

Call wc_AesSetKey() instead, as the PPC asm variant already does. Like
PPC, this makes Direct reject key sizes compiled out with
WOLFSSL_AES_128, WOLFSSL_AES_192 or WOLFSSL_AES_256.
2026-10-23 08:56:15 +02:00
Tobias Frauenschläger
6c712d628e Close three gaps around a key a SETKEY device claims
wc_AesGetKeySize() reports aes->keylen for a key a crypto callback
device owns, so the MAX3266X ECB and CBC entry points, which take a
zero return from it as proof of a key, ran the TPU on the wiped
all-zero aes->key after a claim and returned success. They now check
WC_AES_KEY_IS_SET() first and return MISSING_KEY, as the generic modes
do. The check is only in effect with MAX3266X_CB, which is also the
only MAX3266X build where a device can claim a key.

The wipe before a claim left aes->tmp and aes->left, so the keystream
of the old key buffered by CTR, CFB or OFB stayed in the context. The
generic, ARM32 and PPC set-key now clear both as well.

The docs told a callback to build a software schedule by calling
wc_AesSetKey() with aes->devId set to INVALID_DEVID. Under
WOLF_CRYPTO_CB_FIND the find callback can map INVALID_DEVID back to
the same device, so that call recurses into the callback without end.
The docs now ask for a devId no device is registered under, as the
test devices in tests/api.c already use.
2026-10-23 08:56:15 +02:00
Tobias Frauenschläger
a5ee666332 Wipe the old GCM H when a device claims an AES key
When a SETKEY device claimed a new key through wc_AesSetKey(),
wc_AesCcmSetKey() or wc_AesSetKeyDirect(), the set-key wiped the old
key schedule and devKey but left gcm.H and the GHASH table M0 of an
earlier software GCM key in the context. Only wc_AesGcmSetKey() wiped
them. gcmKeySet is already cleared on that path, so the stale H could
not be used, but it is key material derived from the old key and goes
with the rest of it.

The pre-claim wipe in the generic, ARM32 and PPC set-key now clears H
and M0 too, through a WC_AES_GCM_WIPE_H() macro that wc_AesGcmSetKey()
uses as well.

The CryptoCb AES set-key tests in test_aes.c took buffer sizes with
sizeof, which is the size of a pointer under WOLFSSL_SMALL_STACK. In
such builds test_wc_CryptoCb_AesSetKey and
test_wc_CryptoCb_AesGcm_EncryptDecrypt failed at their first set-key
with an 8-byte key, and the declined re-key check passed an 8-byte tag
that wc_AesGcmEncrypt() rejects before it asks the device. Both tests
now use the allocation sizes.
2026-10-23 08:56:15 +02:00
Tobias Frauenschläger
6c8b85e0b5 Keep AES modes off a key a device claimed without a schedule
When a WOLF_CRYPTO_CB_SETKEY or WOLF_CRYPTO_CB_AES_SETKEY callback
claims an AES key, wc_AesSetKey() builds no software key schedule. The
wrapper still marked the context keyInstalled because the set-key
returned 0, so a mode the device then declined passed the
WC_AES_KEY_IS_SET guard and ran in software:

- On a fresh context rounds is 0. With the C implementation the mode
  fails with KEYUSAGE_E; with ARM assembly on aarch64 it crashes
  (SIGSEGV in AES_CBC_encrypt).
- On a context that held a software key before, rounds and the
  schedule were still the old key's. The declined mode then succeeded,
  encrypting with the previous key instead of the one just set.

The set-key now drops the old schedule before offering the key to the
device: rounds goes to 0, and the schedule, including the C-fallback and
bitsliced copies, and devKey are wiped so no material from the previous
key outlives the claim. After a claim the context is marked installed
only if the callback built a schedule itself, as the in-tree test
callbacks do by re-running the software set-key. Otherwise a declined
mode returns MISSING_KEY, matching wc_AesInit_Id().
WOLF_CRYPTO_CB_ONLY_AES builds keep the key marked installed, since
there every block goes to the device's ECB callback.

The same wipe also clears the streaming GCM key and the recorded key
length. Otherwise a context that held a software GCM key and was then
re-keyed through wc_AesSetKey() or wc_AesCcmSetKey() kept gcmKeySet, and
the ARM assembly build streamed GCM on the wiped schedule; and a set-key
the device failed left the old key length behind, which
wc_AesGetKeySize() reported as a valid key.

The ARM32 and PPC assembly builds have their own wc_AesSetKey(), which
set keyInstalled on every claim and kept the old schedule. They get the
same reset. On ARM32 a declined CBC on a freshly claimed key returned 0,
with output from the empty schedule. The PPC copy only offers the key
through WOLF_CRYPTO_CB_AES_SETKEY, so it resets on that path alone.

Streaming GCM had the same problem through gcmKeySet, which
wc_AesGcmSetKey() never cleared. After a software GCM key, a key the
device then claimed left the flag set, and with ARM assembly the
streaming API encrypted under the previous key and returned 0.
wc_AesGcmSetKey() now clears gcmKeySet before setting the key and sets
it again only on success. A set-key the device fails returns before
deriving H or copying the key to devKey, so a caller that ignores the
error cannot stream on the wiped schedule. Dropping the key also clears
nonceSet, so a context already set up for a message under the old key
needs a new wc_AesGcmInit() instead of streaming on that key's counter
state.

The flag is set before H is derived, so a failure there, a refused
vector register save in an AES-NI build or a failing wc_AesEncrypt(),
returned an error with gcmKeySet still set and H from the previous key.
That block now clears the flag on failure, as the aarch64 path already
did. Neither failure can be provoked from the API tests.

wc_AesGcmSetKey() also stops skipping H and the devKey copy when devCtx
is set. A key a device owns without a schedule now leaves through the
early return, so devCtx is only still set at that point when the context
has a software schedule: the device built one, or a
WOLF_CRYPTO_CB_AES_SETKEY device declined a re-key and kept its handle
from an earlier claim. In the second case the skip left H all zero, and
the C build produced streaming GCM tags that did not depend on the
ciphertext. The ARM assembly H derivation never checked devCtx.

The wc_CryptoCb_AesSetKey() docs promised a devKey copy and GCM tables
for software fallback in TLS builds, which no build provides, while the
README and the AES CryptoCB key import page promised the opposite with
no conditions. All three now describe what a claimed key leaves behind,
and that wolfCrypt takes a non-zero aes->rounds to mean the callback
built a software schedule. wc_AesGetKeySize() reports aes->keylen for a
key with a length but no rounds, so a callback has no reason to set
aes->rounds just to make key-size queries work. The keyInstalled field
comment in aes.h now says a claimed key counts only with a schedule.

The docs also now say that a callback declining a new key must first
release any key it holds for the object and set aes->devCtx to NULL.
wolfCrypt leaves devCtx alone on a decline, so a callback that kept its
handle went on running the operations it accepts, such as one-shot GCM,
under the old key while the software modes used the new one.

A WOLF_CRYPTO_CB_SETKEY claim now also stores the IV it was given in
aes->reg, as the AES_SETKEY claim and the software set-key already do,
so a device that reads the IV from aes->reg does not get the previous
key's.

test_wc_CryptoCb_Aes_SetKey_Declined claims a key, declines CBC and
expects MISSING_KEY, both on a fresh context and after a software key,
and checks the old key is gone from devKey. It needs
WOLFSSL_AES_REQUIRE_KEY_SET, since without the key-set check the
declined mode fails with KEYUSAGE_E instead.
test_wc_CryptoCb_AesGcm_SetKey covers the same software-then-claimed
sequence for streaming GCM.

test_wc_CryptoCb_AesSetKey checks the same for a key claimed through
WOLF_CRYPTO_CB_AES_SETKEY: CBC, which its device declines, returns
MISSING_KEY.

Both tests also check a set-key the device fails with WC_HW_E: CBC and
streaming GCM then return MISSING_KEY.

test_wc_CryptoCb_AesSetKey has its device release its handle and
decline a re-key to a different key, checks that one-shot GCM then fails
in the callback instead of using the old key, and compares the streaming
GCM tag with one computed in software under the new key.

test_wc_CryptoCb_Aes_SetKey_Declined also checks that a claimed key
takes the IV passed with it and reports its size, and that a failed
set-key reports none. test_wc_CryptoCb_AesGcm_SetKey re-keys a software
GCM key through wc_AesSetKey() and checks that streaming GCM refuses the
claimed key. test_wc_AesGcmStream checks that wc_AesGcmSetKey() on a
context initialized for a message leaves updates waiting for a new
wc_AesGcmInit().
2026-10-23 08:56:15 +02:00
Tobias Frauenschläger
5044ea40c5 Skip GCM H derivation for an AES key a SETKEY device claimed
With WOLF_CRYPTO_CB_SETKEY, a callback that returns 0 for WC_SETKEY_AES
owns the key: wc_AesSetKey() builds no key schedule and leaves rounds at
0. wc_AesGcmSetKey() still derived the hash subkey H from that empty
schedule. With the C implementation wc_AesEncrypt() rejects rounds 0 and
the set-key fails with KEYUSAGE_E. On aarch64 with ARM assembly,
AES_ECB_encrypt() runs with rounds 0 and crashes (SIGSEGV). The
function also copied the plaintext key into devKey, which should not stay
behind once a device owns the key.

wc_AesGcmSetKey() now returns as soon as the device has claimed the key,
before deriving H, marking the streaming API keyed or copying to devKey.
On that return it wipes the GHASH subkey a previous key may have left,
and the key unwrapped from a CAAM blob. One-shot GCM reaches the device
through its callback as before.

Streaming GCM has no callback, so it must refuse such a key. The update
calls already return MISSING_KEY while gcmKeySet is clear, but
wc_AesGcmInit() set up the IV without checking it: given a key and an
IV, it ran the counter block through the empty schedule, with the same
KEYUSAGE_E or SIGSEGV as above. It now sets up the IV only once a GCM
key is set. Without one it keeps the IV cached and returns 0, so the
first update returns MISSING_KEY and a later call that sets the key
picks the IV up. That is also what an IV passed before the key relies
on, which on master crashed on aarch64 and failed with KEYUSAGE_E in C.
An IV longer than a block cannot be cached, since it goes through GHASH,
so that case returns MISSING_KEY and leaves the context unchanged.

The same applies to a key claimed through the older
WOLF_CRYPTO_CB_AES_SETKEY hook. The C code already skipped H when the
callback set devCtx, but the ARM assembly derivation did not, so
test_wc_CryptoCb_AesSetKey crashed in AES_ECB_encrypt on aarch64 with
--enable-armasm. Both hooks signal ownership by leaving rounds at 0,
which the XTS entry points already rely on.

test_wc_CryptoCb_AesGcm_SetKey registers a device that claims the key and
performs GCM itself, checks the result against software, and checks that
streaming GCM refuses the key in both wc_AesGcmInit() and the update.

test_wc_CryptoCb_AesSetKey also checks that streaming GCM refuses an
AES_SETKEY claim, and test_wc_AesGcmStream that an IV set before the key
is used once the key arrives, through wc_AesGcmInit() with the key or
the first wc_AesGcmInit() after wc_AesGcmSetKey(), and that a long one
is refused.
2026-10-23 08:56:15 +02:00
Tobias Frauenschläger
6ced6dd801 Keep a cached AES-GCM or AES-CCM nonce when the key is set
wc_AesGcmSetIV(), wc_AesGcmSetExtIV() and wc_AesCcmSetNonce() cache the
nonce in aes->reg, with its length in aes->nonceSz. wc_AesGcmSetKey()
and wc_AesCcmSetKey() then call wc_AesSetKey(), which ends by loading an
IV into aes->reg: an all-zero block for GCM, and zeros for CCM, which
passes no IV. A key set after the nonce therefore left nonceSz in place
and the nonce itself zeroed, and the next operation used an all-zero
nonce:

- wc_AesGcmSetIV(), wc_AesGcmSetKey(), then wc_AesGcmEncrypt_ex() or
  wc_AesGcmEncryptInit_ex(): ivOut reports zeros, and every message
  under that key gets the same nonce.
- wc_AesGcmSetIV() then wc_AesGcmEncryptInit_ex() with the key: ivOut
  reports the random nonce, the data is encrypted under zeros, and
  decrypting with ivOut fails.
- wc_AesGcmSetExtIV() then wc_AesGcmInit() with the key and no IV, the
  order the wc_AesGcmInit() comment describes.
- wc_AesCcmSetNonce(), wc_AesCcmSetKey(), then wc_AesCcmEncrypt_ex().

A GCM nonce reused under one key exposes the XOR of the plaintexts and
lets the GHASH key be recovered, so tags can be forged. A reused CCM
nonce exposes the XOR of the plaintexts too. EVP and TLS are not
affected: EVP passes the IV again together with the key, and TLS sets
the key before the nonce.

Both set-key functions now hand the cached nonce to wc_AesSetKey() when
one is set, so aes->reg keeps it. The devcrypto wc_AesGcmSetKey() and
the TI AesAuthSetKey(), behind its GCM and CCM set-key, zeroed the nonce
the same way and get the same change. The kcapi and AF_ALG
wc_AesGcmSetKey() never write aes->reg.

The GCM and CCM nonce uniqueness tests now also cache a nonce before
setting the key and check that wc_AesGcmEncrypt_ex() and
wc_AesCcmEncrypt_ex() report it. test_wc_AesGcmStream checks that a
nonce from wc_AesGcmSetIV() followed by wc_AesGcmEncryptInit_ex() with
the key decrypts with the reported ivOut.
2026-10-23 08:56:15 +02:00
philljj
06d4408d87 Merge pull request #11799 from padelsbach/swdev-falcon
Tests: add Falcon to swdev for crypto cb testing
2026-10-22 22:30:20 -05:00
philljj
0ea955abf6 Merge pull request #11836 from Frauschi/fix-dtls13-null-cipher-test
Report failures from test_wolfSSL_dtls13_null_cipher
2026-10-22 22:04:32 -05:00
philljj
c7803a1094 Merge pull request #11719 from julek-wolfssl/fix/tls13-stale-session-id
Only send session ID in TLS 1.3 ClientHello when resuming
2026-10-22 17:13:19 -05:00
Chris Conlon
278e98f952 Merge pull request #11798 from aidangarske/remove/whitewood-netrandom
Remove Whitewood netRandom support
2026-10-22 16:01:19 -06:00
philljj
17302ea446 Merge pull request #11831 from rlm2002/fipsV5customSHAKEPQ
Add V523_NONFIPS_SHAKE dev opt-in for FIPS v5 SHAKE128/256
2026-10-22 16:54:19 -05:00
philljj
a4bb52ba08 Merge pull request #11840 from Arpan0995/slhdsa-signhash-none-bad-func-arg
SLH-DSA: return BAD_FUNC_ARG for WC_HASH_TYPE_NONE from SignHashWithRandom and SignHashDeterministic
2026-10-22 16:11:13 -05:00
Ruby Martin
9f27dba749 Add V523_NONFIPS_SHAKE dev opt-in for FIPS v5 SHAKE128/256
FIPS builds at v5.2 or older force WOLFSSL_NO_SHAKE128/256 on in
settings.h, which blocks ML-DSA and ML-KEM's internal SHAKE hashing
under --enable-fips=v5. Adds a CFLAGS opt-in, V523_NONFIPS_SHAKE,
that restores SHAKE128/256 for exactly the cert4718 (5.2.1) build.

- configure.ac, src/include.am, wolfssl/wolfcrypt/include.am: build
  and install the paired shake.c/shake.h from the internal fips repo
  overlay only when the opt-in and FIPS version match, validating
  both files exist first
- hash.c, asn.c, wc_mldsa.c/.h, benchmark.c, test.c, test_sha3.c:
  include shake.h ahead of their first sha3.h/asn.h include, since
  asn.h can reach sha3.h unguarded via ed448.h/wc_lms.h/wc_xmss.h
  first
- wc_mlkem.h: the one exception - its override must come after its
  sha3.h include, since sha3.h processes first there with SHAKE off,
  and shake.h's own fallback typedef and prototypes are what actually
  declare wc_Shake
- every override site uses the same
  defined(WOLFSSL_SHA3) && defined(V523_NONFIPS_SHAKE) guard
- registers V523_NONFIPS_SHAKE in .wolfssl_known_macro_extras, which
  check-source-text otherwise flags as unrecognized
2026-10-22 11:18:53 -06:00
philljj
058fc6101b Merge pull request #11428 from julek-wolfssl/fix/get0-pubkey-borrowed
X509: cache decoded public key, give get0_pubkey borrowed semantics
2026-10-22 11:22:30 -05:00
Paul Adelsbach
c1e1fea3b6 PR feedback: add guards WOLFSSL_SHUTDOWNONCE to test cases 2026-10-21 17:24:47 -07:00
Paul Adelsbach
94b375ec9d Tests: add Falcon to swdev for crypto cb testing 2026-10-21 17:13:12 -07:00
Tobias Frauenschläger
6a9016769e Continue on SNI warning_return in TLS 1.3, drop stale KeyUpdates
A second review of the close_notify changes found:

- An SNI callback returning warning_return made a TLS 1.3 server send
  an alert at warning level and carry on with the handshake. TLS 1.3
  error alerts are always fatal (RFC 8446 Section 6.2), so the previous
  commit marked the connection fatally closed and the server then sent
  its whole flight on a connection that refused every write. The
  client had already given up, as it treats that alert as fatal too.
  warning_return is what OpenSSL-compatible callbacks return as
  SSL_TLSEXT_ERR_ALERT_WARNING to mean "carry on without acknowledging
  the name", and OpenSSL handles it in TLS 1.3 by sending no alert and
  continuing. Do the same. That was the only TLS 1.3 path sending a
  warning-level error alert, so SendAlert_ex() goes back to checking
  the level alone.
- A callback that leaves the alert description unset left it at 0,
  close_notify, unless OPENSSL_EXTRA and WOLFSSL_EXTRA_ALERTS made it
  unrecognized_name. fatal_return then sent a fatal close_notify, and a
  TLS 1.2 warning_return sent a warning close_notify, which ends the
  connection the callback asked to continue. Send handshake_failure for
  fatal_return and no alert for warning_return instead, which still
  does not tell the client that the name was unknown.
- With WOLFSSL_RW_THREADED, a KeyUpdate request read before our
  close_notify left sendKeyUpdate set. SendData() skipped it without
  clearing it, wolfSSL_dtls13_pending_work() kept reporting work and
  wolfSSL_dtls13_do_scheduled_work() sent the KeyUpdate after our
  close_notify. The response is now dropped once the write side is
  closed, and pending_work() no longer reports it.
- The CertificateRequest check returned before parsing, so a malformed
  post-handshake request after close_notify was accepted silently and
  the trace was left unbalanced. The message is now validated and only
  ignored just before its context would be stored.

Merge the close_notify and fatal alert refusals in SendData(), which
differed only in the error code, and drop a comment that described
ssl->error rather than sentNotify.

Move test_dtls13_no_key_update_after_close_notify to the dtls group,
add test_dtls13_scheduled_work_after_shutdown, and replace
test_tls13_sent_warning_error_alert_is_fatal with
test_tls_sni_callback_alerts covering TLS 1.3 and TLS 1.2, warning and
fatal returns, with and without an alert description. Keep two test
comments within 80 columns.
2026-10-21 12:14:01 -07:00
Tobias Frauenschläger
d76eb72c5b Close the remaining paths that write after close_notify
Review of the previous commit found more records, state changes and
misleading errors after our close_notify or a fatal alert:

- A TLS 1.2 server that sent close_notify still reset its handshake
  state for a renegotiation ClientHello, and with
  WOLFSSL_OP_NO_RENEGOTIATION, or when a DTLS epoch would wrap, still
  sent a no_renegotiation alert. It now skips the message, as the
  client does for a HelloRequest.
- An ignored post-handshake CertificateRequest was still parsed, and
  its context stayed linked on ssl->certReqCtx, so a peer could grow
  that list without bound while we waited for its close_notify. The
  message is now skipped before parsing.
- Dtls13DoScheduledWork() still sent the KeyUpdate that
  Dtls13CheckAEADFailLimit() schedules after too many failed
  decryptions.
- SendAlert_ex() only marked the connection closed for alert_fatal.
  In TLS 1.3 every alert except close_notify and user_canceled is
  fatal, which DoAlert() already applies on receipt. An SNI callback
  returning warning_return sends such an alert, after which the server
  could still send 0.5-RTT application data.
- A refused write reported the positive WOLFSSL_ERROR_WANT_READ, which
  wolfSSL_peek() leaves behind after a post-handshake message, instead
  of SSL_SHUTDOWN_ALREADY_DONE_E or SOCKET_PEER_CLOSED_E, so an event
  loop would wait for readability and retry forever.

Add IsWriteClosed() for the sentNotify || fatalAlert check that now
guards every one of these paths.

Extend test_tls12_no_renegotiation_after_close_notify (handshake state
and WOLFSSL_OP_NO_RENEGOTIATION) and
test_tls13_no_pha_response_after_close_notify (certReqCtx), and add
test_tls13_refused_write_after_peek,
test_dtls13_no_key_update_after_close_notify and
test_tls13_sent_warning_error_alert_is_fatal.
2026-10-21 12:14:01 -07:00
Tobias Frauenschläger
2fd8dc2375 Do not answer the peer after our close_notify
The previous commit stopped wolfSSL_write() from building handshake or
KeyUpdate records after a fatal alert or our close_notify. Records the
peer provokes still went out after that point:

- wolfSSL_negotiate() in ReceiveData(), including the secure
  renegotiation one in its loop. After wolfSSL_SendUserCanceled()
  cancelled a handshake, a wolfSSL_read() still finished the handshake
  and sent the Finished.
- DoTls13KeyUpdate() answering a peer's update_requested. This is
  reached from wolfSSL_read() and from the wolfSSL_shutdown() loop
  waiting for the peer's close_notify.
- A post-handshake CertificateRequest, answered with Certificate,
  CertificateVerify and Finished from inside ProcessReply().
- A TLS 1.2 HelloRequest, which started a renegotiation from
  ReceiveData().
- The write side of a write duplicate sending a PHA response or
  KeyUpdate delegated by the read side.

Skip all of them once sentNotify or fatalAlert is set. A peer KeyUpdate
still moves our receive keys forward, so a half-closed connection keeps
reading.

Extend test_tls13_no_handshake_after_user_canceled with a read, and add
test_tls13_no_key_update_response_after_close_notify (read and
shutdown), test_tls13_no_pha_response_after_close_notify,
test_tls13_write_dup_no_response_after_close_notify and
test_tls12_no_renegotiation_after_close_notify (HelloRequest and
ClientHello).
2026-10-21 12:14:01 -07:00
Tobias Frauenschläger
24c44ac717 Do not build handshake or KeyUpdate records after close_notify
SendData() refuses new application data once a fatal alert was seen or
our close_notify was sent, but wolfSSL_negotiate() and the
WOLFSSL_RW_THREADED KeyUpdate response ran before that check. After
wolfSSL_SendUserCanceled() cancelled a handshake, a wolfSSL_write()
still finished the handshake and sent the Finished after the
close_notify, and a KeyUpdate response queued by the read side went out
the same way.

Skip both once sentNotify or fatalAlert is set. The refusal stays after
SendBuffered() and RetrySendAlert(), so a close_notify left in the
output buffer by WANT_WRITE is still flushed.

Add test_tls13_no_handshake_after_user_canceled and, for
WOLFSSL_RW_THREADED builds, test_tls13_no_key_update_after_close_notify.
2026-10-21 12:14:01 -07:00
Tobias Frauenschläger
f7d49da50a Let wolfSSL_shutdown() finish after a refused write
A write refused after our close_notify leaves SSL_SHUTDOWN_ALREADY_DONE_E
in ssl->error, and ProcessReply() refuses to run in that state, so the
second wolfSSL_shutdown() waiting for the peer's close_notify failed.
Clear the error in wolfssl_shutdown_recv_close_notify() before reading,
as ReceiveData() already does. Keeping the error, rather than not setting
it, lets wolfSSL_get_error() report why the write failed in builds that
have no error queue.

Add test_tls13_shutdown_after_refused_write, wrap the expected error
codes of the new tests in WC_NO_ERR_TRACE(), and drop the version nibble
rewrite in test_dtls12_export_import_v6 that test_export_options_v6()
already does.
2026-10-21 12:14:01 -07:00
Tobias Frauenschläger
c2cad5c1bd Keep client and server cache entries in separate rows
The session cache is shared by every CTX in the process, and a
connection between a client and a server in the same process stores two
entries under the same session ID, one per side. Both landed in the same
row, so each connection used two of the row's three slots in the default
cache. When the next connection's ID hashed to the same row, its two
inserts wrapped around and overwrote the oldest entry of the first
connection.

Since the client caches its session in DoFinished(), after the server
verified the Finished, the oldest entry is the server's, so the server
could no longer resume the first session. That made test_session and
test_session_handling in the CPython CI fail intermittently with
session_reused False.

Hash client entries into the row after the one their ID maps to, so the
two sides of a connection never compete for the same row. This changes
where a persisted cache keeps its client entries, but
WOLFSSL_CACHE_VERSION 4 has not been in a release yet, so it covers the
new mapping and caches saved by released builds (version 3) are still
rejected on restore.

The new test only builds with three or fewer sessions per row, since two
connections cannot evict anything from a larger row, and reports a skip
when it finds no pair of connections sharing a row.
2026-10-21 12:14:01 -07:00
Tobias Frauenschläger
42636db290 Refuse application writes only after a fatal alert or close_notify
SendData() refused to write whenever options.isClosed was set, to stop
application data after a fatal alert (RFC 8446 Section 6.2). isClosed is
also set when the transport reports a plain EOF, so a server could no
longer answer a request whose client had already closed its socket.
The write failed without reaching the socket, which reports
SSL_ERROR_SYSCALL with a stale errno instead of EPIPE or ECONNRESET.
nginx logs that at crit level, which made the ssl_stapling.t "no crit"
check in the nginx CI fail intermittently.

Track a fatal alert, sent or received, in a new options.fatalAlert bit
and check that in SendData() instead. A transport EOF goes back to
letting the write reach the socket, as it did before.

Also refuse writes once our own close_notify has been sent, as OpenSSL
does. The isClosed check only caught that when an EOF happened to
arrive after the close_notify. The refused write sets
SSL_SHUTDOWN_ALREADY_DONE_E, which ReceiveData() clears, so the
application can still read the peer's remaining data and its
close_notify.

Session export writes fatalAlert as its own options byte. Export version
7 has not been in a release yet, so it takes the byte without a new
version number; version 7 blobs written by master before this change no
longer import. A version 6 blob only has isClosed, so the importer takes
it as the fatal alert state too, as SendData() did before.
2026-10-21 12:14:01 -07:00
Emma Stensland
bda00f528c Tests: Pin validity dates in test_dual_alg_ecdsa_mldsa 2026-10-21 12:40:58 -06:00
Arpan Sharma
c5f1d816fc LMS: fix no-sha256-256 test failure and lms=shake256 build
test_wc_LmsFeatureCoverage ran the SHA-256/256 round trip
unconditionally and made an L2/H5/W8 key, which only the SHA-256/256
family has, so --group lms failed with --enable-lms=...,no-sha256-256.
Guard both on !WOLFSSL_NO_LMS_SHA256_256, as the other SHA-256/256
tests in the file are.

--enable-lms=shake256 did not turn on SHA-3 and SHAKE256, so a build
without them failed to compile. Turn both on for it in configure, as
for ML-KEM, ML-DSA, FrodoKEM and Falcon, and for WOLFSSL_LMSSHAKE256
in CMake, as for WOLFSSL_FALCON.
2026-10-21 12:58:46 -05:00
Emma Stensland
209ae2b2fd Add tests for PQC verify clearing *res on error
Falcon: device success, silent device, device error, decline and bad arg.
Whitebox: drive the dispatchers' *res clear with NULL res and ret == 0.
2026-10-21 11:21:09 -06:00
Aidan Garske
274193c166 Remove Whitewood netRandom support 2026-10-21 09:52:05 -07:00
philljj
b0afe82085 Merge pull request #11238 from padelsbach/crypto-cb-frodo
Add crypto callback for Frodo-kem
2026-10-21 10:31:26 -05:00
Tobias Frauenschläger
b8b2fed768 Report failures from test_wolfSSL_dtls13_null_cipher
The test returned TEST_SUCCESS whatever its checks found, so the
failing shutdown check was never reported. With session tickets
enabled, the client's second wolfSSL_shutdown() processed the server's
unread NewSessionTicket and returned before the server's close_notify,
and once that is drained the server's second call stops at the
client's ACK the same way.

Read once on each side before shutting down, so each second
wolfSSL_shutdown() finds the peer's close_notify, and return
EXPECT_RESULT().
2026-10-21 15:48:17 +02:00
Tobias Frauenschlaeger
296ef4be79 Hold the EC point formats in one allocation
The point formats extension stored its formats as a linked list with one heap
node per one byte value, which is the shape the supported groups extension had
before. A node costs sixteen bytes to hold one.

Replace the list with a block holding a count, a capacity and a flat array,
matching what the supported groups extension does now. The block starts with
room for four formats, which is more than are defined, and doubles if a caller
adds more.
2026-10-21 11:31:28 +02:00
Tobias Frauenschlaeger
caafb688c3 Keep the max fragment length code in the extension
TLSX_UseMaxFragment() asked the allocator for a single byte to hold the length
code the peer is told about, and freed it when the extension went away. The
TLSX object already carries a value field for extensions whose data is a
scalar, which is how the PSK key exchange modes and early data extensions
store theirs, so use that and hold no data at all.
2026-10-21 11:31:28 +02:00
Tobias Frauenschlaeger
d59405785e Store the trusted CA identifier with its object
TLSX_TCA_New() allocated the object, then the identifier, and freed the object
again on every path where the identifier was missing or the wrong size. Check
the type and size first and put the identifier in a flexible array at the end
of the object, so the failure paths are early returns and there is one buffer
to free.

TLSX_TCA_New() never makes a SHA-1 or X509 name entry without an identifier,
so TLSX_TCA_Write() drops its fallbacks for a missing one.
2026-10-21 11:31:28 +02:00
Tobias Frauenschlaeger
9b6f9cc50c Store the ALPN protocol name with its object
TLSX_ALPN_New() allocated the object and then the protocol name it points at.
A client offering several protocols paid two allocations for each. Put the
name in a flexible array at the end of the object instead, so TLSX_ALPN_Free()
has one buffer to release.

The name can no longer be NULL, so TicketAlpnHash() and the ALPN tests drop
their NULL checks on it.
2026-10-21 11:31:28 +02:00
Tobias Frauenschlaeger
0a23c155de Store the SNI host name with its object
TLSX_SNI_New() allocated the object and then the host name it points at, so
every server name costs two allocations and two frees. Put the name in a
flexible array at the end of the object instead. A union cannot hold one, so
the single member data union goes and the name is sni->host_name.

The host name is the only SNI type there is, so the switch that picked the
allocation to make becomes an early return for any other type, which is what
the default case did anyway.
2026-10-21 11:31:27 +02:00
Arpan Sharma
2fac436ee9 SLH-DSA: SignHashWithRandom returns BAD_FUNC_ARG for WC_HASH_TYPE_NONE
wc_SlhDsaKey_SignHashWithRandom() returned NOT_COMPILED_IN for
WC_HASH_TYPE_NONE, and so did wc_SlhDsaKey_SignHashDeterministic(),
which calls it. wc_SlhDsaKey_SignHash() and wc_SlhDsaKey_VerifyHash()
return BAD_FUNC_ARG for the same input from an identical check, and
the English doxygen for all four says BAD_FUNC_ARG. Return
BAD_FUNC_ARG here too, and check both functions for it in
test_wc_slhdsa_sign_hash.

Add the hashType BAD_FUNC_ARG line to the Japanese doxygen of the four
functions, as in the English. Fix three "NOT_COMPILED in" typos in the
function comments, and a SignHash() comment that said a bad hashSz
still draws from the DRBG.
2026-10-20 13:04:14 -05:00
philljj
c177f861d0 Merge pull request #11576 from douzzer/20260925-WC_RNG_RBGC_STRATUM_IMMUTABLE
20260925-WC_RNG_RBGC_STRATUM_IMMUTABLE
2026-10-20 10:15:26 -05:00
JacobBarthelmeh
5f9ab72a47 Merge pull request #11733 from holtrop-wolfssl/f-7072
Use stored hash type for generic hash APIs
2026-10-19 16:24:31 -06:00
Josh Holtrop
eee10875a7 hash.c: check type argument in both debug and non-debug builds
F-7072.
2026-10-19 14:19:21 -04:00
philljj
abeea08c7d Merge pull request #11606 from Frauschi/fenrir
Static memory, session cache, DH reuse and WOLF_C89 print fixes
2026-10-19 11:41:04 -05:00