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.
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.
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.
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.
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.
An IDE/MCUEXPRESSO/RW612 directory with a known-good user_settings.h and the
steps to build the port against the SDK rather than Zephyr, plus the port
README's pointer to it. The README itself grew alongside the offloads it
documents.
The port was reachable only through the Zephyr module, on the assumption that
NXP's els_pkc is a Zephyr artifact. It is not: els_pkc is NXP's CLNS middleware,
shipped as an MCUXpresso SDK component that additionally carries a Zephyr module,
so a project using the SDK directly should be able to build the port too.
Add --with-els-pkc=DIR alongside --with-mcux-sdk=DIR for the SoC device headers
its platform layer includes, and --with-els-pkc-platform=SOC defaulting to rw61x.
The component include directories are globbed rather than enumerated: the list
runs to roughly fifty entries and moves with each vendor release.
The port states its prerequisites as #error in els_pkc_port.h, so promote
WOLF_CRYPTO_CB_COPY and WOLF_CRYPTO_CB_FREE here the way --enable-rtl8735b does.
A wrong or missing path is diagnosed at configure time, naming the option, rather
than several hundred lines into the build with a missing mcuxClEls.h.
The vendor library stays link-only, so the archive carries unresolved mcuxCl*
references by design, as the SE050 port does.
Serves WC_ALGO_TYPE_SEED as well as WC_ALGO_TYPE_RNG, so wolfCrypt's own
Hash-DRBG is seeded from the hardware rather than only the direct generate path
being accelerated.
The engine takes at least four bytes and only whole words. With the driver's
parameter checks compiled out, a sub-word length would program the DMA past the
end of the caller's buffer, and odd sizes are ordinary (wc_RNG_GenerateByte), so
the tail comes from a word-sized scratch.
The input must arrive already padded to a whole block per SP 800-38B, 0x80 then
zeros, while inputLength stays the true pre-padding count, because that count is
what selects subkey K1 or K2.
Only the last block needs holding back, since only it takes the other subkey, so
everything before it goes in a single command. Every ELS command costs a fixed
~8us for AES, measured by sweeping buffer size on frdm_rw612, so a command per
16-byte block is what throughput is actually made of: AES-128-CMAC over 1KB runs
at 7.2 MiB/s this way against 775 KiB/s one block at a time.
pMac is [in, out] and carries the intermediate state, so it lives in the caller's
Cmac rather than in a pool.
The whole Init/UpdateAad/UpdateData/Finalize sequence runs under one lock, which
it can because the callback hands over the entire message at once.
Every stage consumes whole blocks: the final partial data block is zero-padded
with msgendw carrying its real byte count, and the AAD length reaches the
hardware only through Finalize. An exact block multiple needs no padding and so
comes out correct either way, which means a test exercising only 16, 32 and 64
bytes passes against a port that is wrong for every other length.
J0 is IV || 0x00000001, the single-block form Aead_Init takes; other IV lengths
need the GHASH-based derivation through Aead_PartialInit and are declined.
Finalize outputs the expected tag on decrypt rather than checking it, so the
comparison is done here in constant time, and a mismatch clears the plaintext so
a caller that ignores the return value is not handed unauthenticated data.
Adds the AES cipher modes, and with them the slot reference that lets a
wolfCrypt key name a key living inside the ELS key store.
A callback sees only key->id[], its length and the devId, so the reference is
self-describing and travels as the key's id blob: sixteen bytes of magic,
version, key class, slot, flags and eight bytes binding it to a public point.
wolfPSA stores the same bytes for a vendor-location key. Fields are never
redefined; the version bumps and the format appends. Each key class maps onto
exactly one ELS permission bit and one entry point, and every entry point reads
the slot's properties back and checks that permission before issuing a command,
because a permission violation resets the part instead of returning an error.
AES-192 and any trailing partial block decline to software: ELS knows only 128-
and 256-bit keys and only whole blocks.
CBC chaining is maintained here. The documentation says ELS "will always read
and write" pIV, but it only reads it, so aes->reg still held the original IV
after a one-block encrypt: correct for a single call and wrong from the second
onward. The next IV is the last ciphertext block either way, saved before an
in-place decrypt overwrites it. cphsie/cphsoe are set only for CTR; for CBC they
are documented as ignored, but setting them made ELS treat pIV as an internal
state blob and produce output that was self-consistent yet disagreed with
software.
The ECC classes include a key generation seed, the ukgsrc permission, for a
P-256 key that arrives as its scalar and becomes a key pair only once the engine
generates from it. WC_ELSPKC_KEY_MAX names the last class, so the bounds checks
on building and parsing a reference stay as they are when a class is added.
ELS serves SHA-2 in all four widths. SHA-384 and SHA-512 share the engine's
512-bit state and 1024-bit block, so they differ from SHA-256 only in the mode
selector, a 128-bit padding length field, and SHA-384 taking the leading 48
bytes of the final state.
HMAC benefits without an arm of its own: HmacKeyInitHash passes the Hmac's devId
to the inner hash, so HMAC-SHA384, HMAC-SHA512, HKDF and the SHA-384 TLS PRF all
compress on the engine.
The ELS HMAC command stays unused. It is one shot, with no equivalent of the
hash command's state load, while the wolfCrypt callback is incremental, so
serving it would mean buffering whole messages on parts routinely built without
an allocator.
Routes wolfCrypt through the on-chip EdgeLock subsystem (ELS) on RW612 via the
crypto callback interface, with SHA-256 as the first offloaded primitive.
Anything the hardware does not implement declines to software, so enabling the
port never removes functionality.
Three properties of the hardware shape the design.
ELS is one peripheral with global busy state: every operation is an _Async call
followed by a wait, and nothing may start between the two, so one lock is held
across each pair.
ELS answers a rejected request by signalling the tamper controller, which
resets the SoC, rather than by returning an error. Validation therefore happens
in software before the call: getting it wrong reboots the device.
mcuxClEls_WaitForOperation() busy-spins with no timeout, and would do so
holding that lock. Completion is taken from the ELS interrupt instead, wired
with IRQ_CONNECT because the IRQ has no devicetree node, with polling as the
fallback for ISR context and for anything before the IRQ is armed. A late
interrupt degrades to the synchronous wait rather than cancelling: measured,
MCUXCLELS_RESET_CANCEL on an in-flight operation reboots the SoC.
For SHA-256 the engine round-trips its intermediate state (hashoe writes it,
hashld reloads it), so the state lives in the caller's wc_Sha256 and the lock
covers one call. Holding it from update to final deadlocks TLS 1.3, which keeps
several transcript hashes alive at once. ELS never pads, so the port carries the
residual block and appends the padding itself.
Registration happens inside wolfCrypt_Init(), which is also what zeroes the
callback device table. Zephyr gains CONFIG_WOLFSSL_SYS_INIT to run that at boot
from POST_KERNEL rather than leaving it to whichever library call happens to be
first, which starts to matter once initialization brings up a peripheral. It
defaults on when the port is enabled and stays available on its own, since
nothing about it is specific to this hardware. The hook calls wolfSSL_Init() so
the TLS layer is initialized too, and wolfCrypt_Init() in a WOLFCRYPT_ONLY
build, where wolfSSL_Init() is not compiled.
The port's Kconfig symbol is restricted to SOC_SERIES_RW6XX, the only series for
which els_pkc exports src/platforms/<soc>, where mcux_els.h lives.
The CAAM AEAD, CBC/CTR and ECB helpers took a successful
wc_AesGetKeySize() as proof of a key and then handed aes->devKey to the
hardware. For a key another device claimed, wc_AesGetKeySize() now
succeeds from keylen while devKey is wiped, so CAAM ran GCM, CCM, CBC,
CTR or ECB under an all-zero key and returned 0, where it used to return
BAD_FUNC_ARG.
Return MISSING_KEY when aes->rounds is 0 and no black key is set, as the
MAX3266X callbacks now do. A black key leaves rounds at 0 and keeps
working. wc_CAAM_AesCtrEncrypt is covered through the CBC/CTR and ECB
helpers.
WOLF_CRYPTO_CB defines MAX3266X_CB, which turns MAX3266X_AES off in
aes.c, so with callbacks the TPU runs from the wc_MxcCb_Aes* functions
and the key checks in the MAX3266X_AES ECB and CBC functions never see a
claimed key. They stay for builds without callbacks that force
WOLFSSL_AES_REQUIRE_KEY_SET.
For a key another device claimed, wc_AesGetKeySize() now succeeds from
keylen, so the callbacks encrypted with the wiped all-zero devKey and
returned 0, where they used to return BAD_FUNC_ARG. Return MISSING_KEY
when aes->rounds is 0 instead. keyInstalled would not do here:
WC_AES_KEY_IS_SET() is always true under WOLFSSL_NO_AES_KEY_SET_CHECK
and WOLF_CRYPTO_CB_ONLY_AES, while a key claimed without a schedule has
no rounds in every build and a key the TPU serves always has them.
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.
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.
The comment on a successful WC_SETKEY_AES claim said rounds stays 0
because there is no software key schedule. A callback may now build
one and set rounds, so the comment is cut to the line the ARM32 and
PPC copies use.
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.
The wc_AesGcmSetKey() doxygen did not say what this series changed for
its callers: a new key drops the streaming state, so the update calls
return MISSING_IV until the next wc_AesGcmInit(); a nonce cached with
wc_AesGcmSetIV() or wc_AesGcmSetExtIV() is kept; and a key a crypto
callback device claims without a software schedule leaves streaming GCM
at MISSING_KEY.
The callback contract told a callback that sets aes->rounds to write a
key schedule into aes->key. That is not enough in every build: the
bitsliced and C-fallback builds read other fields, which the pre-claim
wipe has just zeroed, and the AES-NI and ARMv8 flags and the buffered
keystream would still describe the previous key. README.md, the
doxygen page and the cryptocb.h docs now say to build the schedule by
calling wc_AesSetKey() on the same object with aes->devId set to
INVALID_DEVID, as the existing test callback does, which sets all of
that. A WOLF_CRYPTO_CB_AES_SETKEY request carries no key direction, so
only a WOLF_CRYPTO_CB_SETKEY callback can do this.
Two comments in aes.c are reworded.
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.
The TI wc_AesGcmEncrypt() calls wc_local_AesGcmCheckTagSz(), but aes.c
defines it in the part that a WOLFSSL_TI_CRYPT build replaces with
ti-aes.c, so a TI build with HAVE_AESGCM failed to link. The definition
now sits above the ti-aes.c include, so every backend gets it.
The TI GCM decrypt also passed its word32 tag buffer to
ConstantCompare() in two places without the byte cast it uses
elsewhere, which GCC 14 rejects as an incompatible pointer type.
The PowerPC assembly build has its own wc_AesSetKey(), and it only
called the older WOLF_CRYPTO_CB_AES_SETKEY hook. With
WOLF_CRYPTO_CB_SETKEY a device never saw the key: the context always
got a software schedule, so a device that keeps its keys could not own
one, unlike in every other build.
The PPC set-key now calls wc_CryptoCb_SetKey() the same way the ARM32
one does, and handles a claim the same way: the old schedule and devKey
are wiped first, and the key counts as installed only if the callback
built 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().
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.
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.
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