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.
wc_AesSetKey now says that a mode the callback does not handle returns
MISSING_KEY once a device claims the key, and that a device error can
be returned. wc_AesCcmSetKey says a cached CCM nonce is kept and lists
its real return values instead of "none".
The SETKEY callback contract also says that a WOLF_CRYPTO_CB_SETKEY
callback must release its key and clear aes->devCtx before returning an
error, because wolfCrypt clears devCtx itself only after an
AES_SETKEY error.
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 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.
A device can write *res and then fail or decline, leaving a stale 1 on
an error return. Clear it in the PQC verify dispatchers, and clear
Falcon's *res on entry to cover early errors and silent devices.
* Adds WC_RNG_INIT_FLAG_DRBG_SHA{256,512}.
* Removes wc_DrbgState_MutexInit() and wc_DrbgState_MutexFree(), and the
associated dependency on wolfCrypt_Init() before first call.
* Existing semantics are preserved exactly, now by compare-and-swap instead of a
lock: the last enabled flavor cannot be disabled (BAD_STATE_E), concurrent
enable/disable calls serialize against one another with no lost updates, and
the setting is read once at each RNG instantiation. (Note that racing threads
on WOLFSSL_NO_ATOMICS targets can lose updates, but can never leave
DrbgType_mask with an invalid value).
* Flags-bearing APIs -- wc_InitRng_ex2() etc -- now honor
WC_RNG_INIT_FLAG_DRBG_SHA{256,512}, overriding the process-wide runtime
setting.
* The refactor eliminates an unneeded process-wide mutex previously taken in
every call to _InitRng(), which was particularly harmful in kernel mode.
State is now tracked by bits in a single atomic unsigned int.
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.
The Doxygen blocks for wolfSSL_update_keys() and
wolfSSL_request_certificate() told callers to compare the return value
against WANT_WRITE (-327). Both implementations convert that internal
code to the public WOLFSSL_ERROR_WANT_WRITE (3) before returning, so the
documented check could never match and a non-blocking caller would fall
into the generic failure branch.
Document WOLFSSL_ERROR_WANT_WRITE, the continuation contract (the
message stays queued and the next wolfSSL_write() sends it; do not call
the API again for the same update), and the QUIC rejection. For
wolfSSL_update_keys() over DTLS 1.3, note that success means the
KeyUpdate was sent, not acknowledged, and that a repeat call while the
ACK is outstanding returns WOLFSSL_SUCCESS without sending.
Fixes#11763
wc_LmsKey_SigsLeft() checked only for a NULL key and then passed
key->params to wc_hss_sigsleft(), which dereferences it. A key that
was only initialized has no parameters, so the call crashed. A
verify-only key (after wc_LmsKey_ImportPubRaw()) reported 1 although
wc_LmsKey_Sign() refuses it. Return 0 for both, as wc_LmsKey_Sign()
rejects such keys before it calls the device. The check is on the
verify-only state and not on WC_LMS_STATE_OK, because
wc_LmsKey_MakeKey() and wc_LmsKey_Reload() call wc_LmsKey_SigsLeft()
while the key is still in WC_LMS_STATE_PARMSET.
The doxygen for wc_LmsKey_SigsLeft() and wc_XmssKey_SigsLeft() said
they return the number of signatures left or a negative error code;
both return 1 or 0. Fix that in English and Japanese, and in the LMS
and XMSS group descriptions.
wc_MlDsaKey_InitId() and wc_MlDsaKey_InitLabel() called
wc_MlDsaKey_SetParams(key, WC_ML_DSA_87) even when their argument
check had failed and the key was never initialized, and ignored its
return value. With ML-DSA-87 compiled in (the default), an argument
error wrote into the caller's uninitialized object, and with the
WC_MLDSA_CACHE_* options or WOLFSSL_MLDSA_DYNAMIC_KEYS it zeroized and
freed whatever pointers that memory held. In builds without
ML-DSA-87 the call failed, so InitId and InitLabel returned 0 with no
parameter set.
Call SetParams only when the key was initialized, return its result,
and use the highest level compiled in, as wc_falcon_init_id() and
wc_falcon_init_label() do with FALCON_MAX_LEVEL.
wc_rng_bank_root_rng_init() now refuses WC_RNG_INIT_FLAG_USE_FULL_MUTEX
with BAD_FUNC_ARG. The bank operates its root through lock-word
operations alone -- the daemon's claims, reinit-in-place
(wc_rng_bank_reinit_rng()), the invalidation walk -- none of which take
the outer mutex, so a full-mutex root was bypassed by every one of them,
and its reinit's teardown (wc_FreeRng_PreLocked() under a lock-word-only
lease) released and destroyed a mutex the bank never took, under any
getter holding it. wc_rng_bank_reinit_rng() gets a matching tripwire
(WRONG_TYPE_OBJECT_E on a WC_RNG_FLAG_FULL_MUTEX instance, before any
mutation), unreachable through the bank's own constructors.
doc/dox_comments/header_files/rng_bank.h: wc_rng_bank_root_rng_init()
documents the refusal (and the existing ALREADY_E).
wolfcrypt/test/test.c: rng_lifecycle_test() tries the flag on a vacated
root (refused, root still vacant), then instantiates and reinstantiates
the root without it. WC_RNG_HAVE_LOCK_FULL_MUTEX builds only; no CI
scenario sets WC_RNG_WANT_LOCK_FULL_MUTEX, so verified locally with
--enable-rng-extras --enable-rng-bank CFLAGS=-DWC_RNG_WANT_LOCK_FULL_MUTEX.
wolfcrypt/src/rng_bank.c:
* wc_rng_bank_fini(): a root pinned by an external (caller-spawned)
child, or held, is now refused before commitment, with the bank fully
intact -- STILL_REFERENCED_E / BUSY_E, as for a member. Previously a
root free failure was only tallied, the bank's refcount and flags were
destroyed, and the retry of wc_rng_bank_free() deallocated the bank
under the child's RBGC_parent. The pre-commit check counts member
pins (released by the member frees) and refuses on any pin beyond
them, regardless of the root's status: a root torn down by a failed
reinit is NOT_INIT with its lock word live and its pins outstanding,
and was previously skipped. The post-commit root free now restores
the refcount and returns on BUSY_E / STILL_REFERENCED_E, as the member
loop does, so a child that arrives after the pre-commit check still
leaves a retryable bank.
* wc_rng_bank_reinit_rng(): WC_RNG_INIT_FLAG_PRESERVE_LOCK is added
only when WC_RNG_HAVE_LOCK, as wc_rng_bank_inst_reinit() already does.
_InitRng() refuses the flag with NOT_COMPILED_IN otherwise, so in a
bank built without the lock facility wc_rng_bank_root_rng_reinit()
tore the root down and could not reinstantiate it; the inst-op gate is
the exclusion in that arm.
wolfcrypt/src/random.c:
* _InitRng(): on an RDRAND-capable CPU with HAVE_INTEL_RDRAND, the DRBG
bypass now refuses caller-supplied seed material with
WRONG_TYPE_OBJECT_E, the instance left uninstantiated. Previously
wc_InitRngNonce_UserSeed() and wc_RNG_global_fallback_init_user_seed()
returned success with the seed silently dropped and the instance
relabeled stratum 0, generating from RDRAND -- a primary-class
instance under a user-seeded name. Instantiating the DRBG anyway is
not an option: wc_RNG_GenerateBlock() reads RDRAND whenever the CPU
has it, DRBG or not. wc_InitRngFixedSeed()'s post-hoc backstop
(which stays, for CUSTOM_RAND_GENERATE_BLOCK) returns the same code.
doc/dox_comments/header_files/random.h: wc_InitRngNonce_UserSeed()
documents the refusal; the previous text described a bypass
"HAVE_INTEL_RDRAND without HAVE_HASHDRBG", which cannot reach the
function (WC_RNG_HAVE_RBGC requires HAVE_HASHDRBG), while the reachable
case went undocumented.
wolfcrypt/test/test.c: rng_lifecycle_test() gains the bank-teardown
scenario (external child of the root; refused fini leaves the bank
generating; held root; torn-down pinned root; convergence once the child
is released) and a lock-less bank root reinit. The latter fails against
the previous rng_bank.c.
linuxkm/module_hooks.c: in the pre-3.17 NMI-context time accessors, read
jiffies after the calibration latch rather than at entry: a calibration
published by another CPU between the two can carry a later tick, and
the unsigned difference wrapped.
linuxkm/linuxkm_wc_port.h: in_task() shim for kernels before 4.10, where
it first appears (include/linux/preempt.h); wc_linuxkm_can_long_loop()
used it unconditionally.
Go back to the one-byte sigSz probe for SignWithRandom() with a
(NULL, 0) message in test_wc_SlhdsaDecisionCoverage(), so the test
stays argument-check only. The signing path for (NULL, 0) is covered
in test.c.
Update the Japanese doxygen for SignDeterministic, SignWithRandom,
Sign and Verify to match the English header.
- Source comments: msg may be NULL if msgSz is 0 for the sign and
verify functions, and Sign(), Verify() and slhdsakey_sign_external()
reject a NULL msg only when msgSz is greater than 0.
- Doxygen: note on the msg parameter, and SignDeterministic() returns
BAD_LENGTH_E for a too-small signature buffer.
- test.c: SignDeterministic() with (NULL, 0) must match the signature
for a non-NULL message of length 0.
- test_slhdsa.c: SignWithRandom() with (NULL, 0) and a full-size
signature buffer signs successfully.
wc_SlhDsaKey_SignWithRandom() rejected msg == NULL for any length, so it
and wc_SlhDsaKey_SignDeterministic() returned BAD_FUNC_ARG for an empty
message passed as (NULL, 0), unlike wc_SlhDsaKey_Sign() and Verify().
Use the same check as Sign() and, like Sign(), replace a NULL message
with a one-byte static stand-in before the crypto callback dispatch.
Update the doxygen and source comments, and add tests for the (NULL, 0)
case.
WC_RNG_RBGC_STRATUM_IMMUTABLE campaign):
* RNG state-invalidation delivery by entropy epoch
* Fork safety for the WC_RNG lock word
* RBGC lineage persistence with a global fallback source
* A uniform "transient vs. verdict" error taxonomy
* Lifecycle hardening that makes WC_RNG safe to reinstantiate under lease-free
observers.
Protocol-level changes (random.[ch]):
* Entropy epoch (WC_RNG_HAVE_ENTROPY_EPOCH, on by default with atomics in
multithreaded builds; WC_RNG_WANT_/WC_RNG_NO_ENTROPY_EPOCH override): a
global event counter, wc_RNG_global_invalidate_entropy(), replaces the
per-instance walk as the primary delivery mechanism for state-invalidation
events (VM fork/resume, hibernation). Each WC_RNG carries a stamp; every
seeding operation captures the epoch (and PID) before acquiring material
and stamps at completion, so an event that lands mid-operation leaves a
stale stamp. rng_entropy_event_propagate() converts {stale stamp,
unlatched} to {latched, swept} lazily, at the instance's next aperture:
"detection is also delivery," and every aperture consulted after a nonzero
wc_RNG_entropy_needs_recovery() is post-purge. wc_RNG_lock_put() reports
an unpropagated increment as NEEDS_RECOVERY_E, so a holder whose last
operation straddled the event learns of it at release. The latch is
cleared by a recovery only when the recovering caller's capture is still
current.
* wc_RNG_entropy_needs_recovery() is the single authoritative verdict on a
standing recovery obligation (latch, stale epoch, PID change, exhausted
reseed budget), and the generate path, the stir path, the pool reader and
the banked-seed consumer all key on it. rng_pid_change_check() is retired
into it: a fork() is an epoch event. The reseed counter is thereby
returned to its SP 800-90A meaning (budget only); invalidation never
spends runway and is never enforced by counter saturation in lock builds.
* Fork safety for the lock word: WC_RNG_LOCK_ORPHANED (error-crypt.h:
OBJECT_ORPHANED_E, -1036; HWPUF_* codes shift down by one). A _HELD word
stamped with another process's PID was held across a fork() by a holder
absent in this process; the first post-fork contender marks it _ORPHANED
(RngLockOrphanCheck(), from every lock getter), applies the invalidation
once as CAS winner, and reports OBJECT_ORPHANED_E. The verdict licenses
nothing to the contender: the owner adopts the lease by
wc_FreeRng_PreLocked() then wc_InitRng_ex2() with the new
WC_RNG_INIT_FLAG_PRESERVE_LOCK (which clears _ORPHANED with the latch); a
live holder's release or completed recovery also clears it.
* RBGC lineage persistence (WC_RNG_HAVE_LOCK && WC_RNG_HAVE_RBGC): a chain
child retains RBGC_parent and pins it by RBGC_refcount (both live for as
long as the lock word, preserved across _PRESERVE_LOCK reinits).
wc_FreeRng() on a pinned parent wipes sensitive state and returns the new
STILL_REFERENCED_E (error-crypt.h) -- the shell must outlive its
children; wc_rng_free() leaks rather than freeing referenced memory.
WC_RNG_FLAG_NO_RBGC_PARENT opts an instance out of being retained
(wc_rng_new*() and *_New() constructors set it, since the void
wc_rng_free() cannot report). HAVE_FIPS forces lock-required parents and
WC_RNG_RBGC_STRATUM_IMMUTABLE, and random.h refuses HAVE_FIPS with RBGC
but without the lock facility.
* Global fallback RBGC source (WC_RNG_HAVE_GLOBAL_FALLBACK_RNG):
wc_RNG_global_fallback_init[_user_seed]() / _get() / _free(), a static
lock-required WC_RNG, freed by wolfCrypt_Cleanup(). PollAndReSeed() and
wc_RNG_DRBG_NextSeedGenerate() climb a ladder for a chain-backed
instance: the retained parent, then the global fallback (under FIPS only
when it qualifies as "a sibling of the parent, an ancestor, or the initial
randomness source" per SP 800-90C), then -- unless the instance is
WC_RNG_FLAG_NO_PRIMARY_SEED -- the primary source. A ladder rung whose
release reports NEEDS_RECOVERY_E voids the reseed (DRBG_NEED_RESEED) or
purges the bank just filled. SP 800-90C qualification of a supplied root
against the retained parent is enforced in wc_RNG_DRBG_ReseedRBGC() and
wc_RNG_DRBG_NextSeedGenerate_RBGC() under FIPS.
* Runtime policy flags: wc_RNG_SetFlags() / wc_RNG_ClearFlags() /
wc_RNG_GetFlags(), limited to WC_RNG_FLAGS_SETTABLE / _CLEARABLE
(FAIL_FAST, ONLY_PRIMARY_SEED, NO_PRIMARY_SEED) and to exclusive
ownership. WC_RNG_FLAG_FAIL_FAST (WC_RNG_INIT_FLAG_FAIL_FAST at birth):
a generate that would otherwise reseed in-line reports instead --
NOT_READY_E for the budget, NEEDS_RECOVERY_E for a latched or stale
instance (RngFailFastVerdict()) -- leaving the DRBG uncondemned;
wc_RNG_Pool_Collect2() sets it transiently on its source so the ring claim
is held for one bounded generate. WC_RNG_FLAG_NO_PRIMARY_SEED /
WC_RNG_INIT_FLAG_NO_PRIMARY_SEED forbids the primary source after
instantiation (chain members that must stay chain-fed; MISSING_RNG_E on an
instance with no chain parent); WC_RNG_FLAG_ONLY_PRIMARY_SEED is the
converse, set transiently by the new wc_RNG_DRBG_Reseed_Now_Primary() for
prediction-resistant constructions on chain-backed instances.
WC_RNG_{INIT_,}FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED is renamed
_UPGRADE_FROM_NEXT_SEED (rng_bank.h: WC_RNG_BANK_FLAG_AUTO_UPGRADE_FROM_
NEXT_SEED). WC_RNG_INIT_FLAG_* retain their shipped values, with the
new flags appended; WC_RNG_FLAG_* are renumbered alongside.
* Transient-vs-verdict taxonomy: WC_RNG_ERR_IS_TRANSIENT() (random.h, with
a mirror in rng_bank.h for boundaries that predate it) names the codes
that mean "not now" -- BUSY_E, NOT_READY_E, NEEDS_RECOVERY_E, RAN_BLOCK_E,
ENTROPY_RT_E, ENTROPY_APT_E. wc_InitRng*() passes them through from the
seed source verbatim (ReseedSourceFailure(), shared by instantiate and
reseed) rather than folding them into RNG_FAILURE_E; rng_bank.c keys its
retry loops on the same set. BUSY_E and MEMORY_E -- a resource that
requires waiting, in a context that can't -- never condemn, even with no
reseed runway (RngGenerateFailure(), now also the single outcome mapping
for wc_RNG_DRBG_Reseed_Now()). wc_RNG_Status() precomputes the code for
a status (NOT_READY_E / RNG_FAILURE_E / DRBG_CONT_FIPS_E) and replaces
the ad hoc status checks throughout. wc_GenerateSeed() with
HAVE_ENTROPY_MEMUSE returns BUSY_E from a context that cannot run the
wolfEntropy source rather than silently substituting a backup source.
* WC_RNG_INIT_FLAG_TEST_USER_SEED: caller-supplied seed material is
health-tested and laid out exactly as module-gathered material (leading
SEED_BLOCK_SZ bytes are the test block, excluded from DRBG input in FIPS
and non-FULL_SEED builds); wc_InitRngFixedSeed() uses it, restoring its
master behavior. Without the flag the user seed is copied at the
instantiate offset, so no supplied byte is discarded and no health test
runs. The strength floors live at the public entry points
(UserSeedSzFloor(): WC_DRBG_SEED_SZ, plus a block under the flag where the
block is excluded; zero refused); _InitRng() keeps only the one-block
backstop. wc_InitRngNonce_UserSeed() refuses without WC_RNG_HAVE_RBGC
(provenance marking lives there) and validates its shape arguments.
Lifecycle hardening (random.[ch]):
* WC_RNG members shared by both DRBG cores -- drbgType, reseedCtr, the
banked-next-seed aperture (nextSeed, nextSeedLen, nextSeedRBGCStratum) --
move from DRBG_internal / DRBG_SHA512_internal into WC_RNG, for safe
lock-free access by lease-free observers; the per-core structs keep the
SMALL_STACK_CACHE newSeed_buf. WC_DRBG_SEED_BUF_SIZE names the gathered
seed length; the generic DRBG functions take the reseed counter by
pointer. NextSeedPtrs() and the per-core duplication it served are gone.
* _InitRng() zeroization (RngInitWipe()) skips the atomic words that can
have lease-free concurrent observers (lock, RBGC_refcount, poolHead/Tail,
nextStirLen, nextSeedLen), which are instead atomically (re)initialized;
the apertures are painted _TORNDOWN at entry and reopened as the last act
before publication, and rng->status becomes DRBG_OK only at the very end.
WC_RNG_INIT_FLAG_PRESERVE_LOCK leaves the lock word and refcount exactly
as found (refusing _LOCK_INITIALLY and _USE_FULL_MUTEX, and insisting the
_REQUIRED bits agree), and discharges an inherited latch by CAS at entry
rather than erasing it. WC_RNG_STATIC_INITIALIZER is provided.
* Pool lifecycle: poolHead carries a 2-bit state above the epoch (UNALLOC,
LIVE, CLAIMED, TORNDOWN). wc_RNG_Pool_Alloc() allocates unclaimed and
publishes by CAS; a writer claims by one CAS at entry and releases by CAS
at every exit; wc_FreeRng() takes LIVE -> TORNDOWN by one CAS before any
irreversible step and returns BUSY_E (nothing mutated) when a claim is
held, never spinning; TORNDOWN refuses re-equipment until the next
_InitRng(). Likewise the credited seed aperture gains
WC_DRBG_NEXT_SEED_TORNDOWN, every ownership transition is a CAS, and a
purge repaints a live claim _PURGED for its holder to unwind rather than
reopening it from under the holder. wc_FreeRng() frees the DRBG after
nulling the pointer at every site, and uninstantiates only the core
matching drbgType.
* wc_FreeRng() in lock builds takes the lease itself (unconditionally, so a
standing latch or condemnation does not block teardown) and refuses a
held instance with BUSY_E; wc_FreeRng_PreLocked() is the form for a
caller already holding the lease, which it retains. The WC_RNG_LOCK_
REQUIRED-but-unheld case is OBJECT_NOT_LOCKED_E. wc_RNG_lock_get_
unconditional() / _put_unconditional() are the internal trivial forms.
* wc_RNG_DRBG_GetReseedCtr() becomes WOLFSSL_LOCAL (the counter is
potentially sensitive internal state); wc_RNG_DRBG_NextSeedGenerate_
Primary() is added; wc_RNG_DRBG_NextSeedCurrent(), wc_RNG_Pool_Current()
and wc_RNG_lock_read() take const. An instance with no DRBG (RDRAND et
al.) never opens its seed aperture, and producers and consumers alike
refuse it with WRONG_TYPE_OBJECT_E (was MISSING_RNG_E, which nothing on
that path returned). A stir refuses with the needs-recovery verdict
(NEEDS_RECOVERY_E) when the instance is quarantined, due, or stale, and
NOT_READY_E only when the interval trips within the call.
* WC_VERBOSE_RNG logging follows the taxonomy: transient codes are never
logged as errors.
rng_bank.c / rng_bank.h:
* struct wc_rng_bank_inst now places its WC_RNG at offset 0, so
WC_RNG_BANK_INST_TO_RNG() is a cast; wc_rng_bank_reinit_rng() (LOCAL)
retires and reinstantiates any bank-owned rng, protected by WC_RNG.lock
under _PRESERVE_LOCK where available (a held root yields BUSY_E) and by
the inst-op gate otherwise; wc_rng_bank_inst_reinit() and
wc_rng_bank_root_rng_reinit() are built on it. A member is reborn only
as a chain child of its root -- a root that cannot serve right now is a
reason to retry, never to substitute a primary reseed.
* wc_rng_bank_reseed_range(): the RBGC arm takes the root's lease for the
chain draw; the walk carries on past a per-instance failure (marking the
instance due) and returns the first such failure, with a code that
aborted the walk (WC_TIMEOUT_E, INTERRUPTED_E, a checkout failure)
outranking it; wc_rng_bank_next_seed_generate_local() purges the member
when the root's release reports an event under its hold, as
wc_RNG_DRBG_NextSeedGenerate()'s chain arm does.
* wc_rng_bank_fini(): a member refusing to free (BUSY_E /
STILL_REFERENCED_E) restores the bank refcount so the caller can and must
retry; the root is freed after the members, so member pins are released
before the root's refcount is judged. The invalidation walk's root arm is
unconditional (a root mid-reinit is NOT_INIT with its lock word live).
Init retry loops key on WC_RNG_ERR_IS_TRANSIENT().
* rng_bank.h: WC_RNG_LOCK_ORPHANED / WC_RNG_LOCK_EXTRA_SHIFT 5 mirrored in
the no-lock compat arm; for the WCv5.2.4 boundary, which carries the
backported RNG services, WC_RNG_HAVE_STIR / WC_RNG_HAVE_RESEED_NOW
capability markers and a wc_InitRngNonceRBGC() perso-argument shim, so
consumers outside the boundary (src/ssl.c, src/ssl_crypto.c) gate on the
markers rather than on FIPS version arithmetic.
linuxkm/lkcapi_sha_glue.c:
* wc_linuxkm_rng_state_invalidate(): with the entropy epoch, bumps the
epoch and materializes only the bank root's latch synchronously
(rng_invalidation_pre_check() keys on it), leaving members to lazy
delivery so a consumer is admitted to a still-unlatched member and
recovers it under its own lease rather than waiting out a quarantine;
without the epoch, the synchronous walk as before. The daemon-less bank
path treats a transient member code from wc_rng_bank_reseed_range() as a
deferred recovery, not a failure.
* Entropy daemon: a transient failure of the last-ditch root reinit is
retried next pass rather than ending the daemon (which had made root
recovery and next-seed feeding end for good on ordinary contention);
the vmgenid poller's immediate root reseed takes the root's lease (it was
OBJECT_NOT_LOCKED_E and void-cast); the kernel-log rule throughout --
transient codes are one pass away and not errors, the SP 800-90B health
verdicts are warnings (WC_LKM_RNG_ERR_IS_HEALTH()), anything else is an
error -- at the recovery, banking, bonus-reseed and reinit arms; the
banking pass's no-DRBG case is WRONG_TYPE_OBJECT_E; busy instances are
counted in the recovery sweep's tally.
* wc_linuxkm_drbg_generate(): the kernel-facing language is two-valued --
-EIO for a verdict (non-transient, or ENTROPY_RT_E / ENTROPY_APT_E),
-EBUSY for every "not now" code; only BUSY_E outside the invalidation
window (every instance held at once: an undersized bank) is logged as an
error. The pooling pass (re)equips any instance it finds poolless each
turn, so no up-front allocation and no special case after a
reinstantiation. Bank sizing rationale documented.
* The _get_random_bytes() callback contract is best-effort, not
unconditional success: a refusal from atomic context immediately after a
global invalidation falls through silently to the native generator.
linuxkm/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v3.patch: drop the
WARN_ONCE on callback failure, and drop the routing of the batched
get_random_{u8,u16,u32,u64}() through the callback (not FIPS-controlled
consumers; get_random_bytes_user() and getrandom() remain fully covered).
Tests:
* wolfcrypt/test/test.c: flags_test() (WCFLAGS) replaces rng_flag_abi_test():
pairwise-disjointness of every bit-flag namespace in the wolfcrypt headers
at compile time and of the hand-numbered registries at run time, against
the symbols rather than literal values; rng_epoch_test() (RNGEPOCH: epoch
propagation on every aperture, mid-gather increments, busy and
out-of-memory seed sources never condemn); rng_lifecycle_test() (RNGLIFE:
RBGC parent pinning and STILL_REFERENCED_E, the destruction latch,
wc_FreeRng_PreLocked() and the _PRESERVE_* reinit, the bank-walk mirror
arm); rng_lock_fork_test() (RNGLKFRK: the orphaned-lease verdict and
adoption across a real fork()); rng_init_flags_test() (contradictory and
not-compiled-in init flags refused) and rng_seed_verdict_test() (transient
codes from the seed source survive wc_InitRng*() verbatim; FIPS v7+ only,
the pinned legacy random.c folds them); rng_seed_test() reactivated (dead
since the seed-size defaults moved into random.h) and steered to SHA-256
in SHA-512-preferring builds; WC_RNG_INIT_FLAG_TEST_USER_SEED arms in the
wc_InitRngNonce_UserSeed() strict-contract block; leak fixes and
goto-teardown in the RBGC-lifetime and pool tests; per-subtest in-kernel
stack high-water marks (WC_LINUXKM_STACK_DEBUG_PER_TEST); WC_RNG moved to
the heap in pkcs7authenveloped_run_vectors().
* tests/api/test_random.c, tests/unit-mcdc/test_random_*whitebox.c: follow
the reseed counter to WC_RNG; forced seed failures use a nonsense code so
the WC_VERBOSE_RNG line stays quiet; condemnation-on-reseed-failure arms
saturate the counter explicitly, since invalidation no longer does.
* doc/dox_comments: every new API and flag; the instantiation-input and
user-seed contracts; reseed/reseed_range return semantics; the next-seed
return taxonomy.
instantiated value (the genealogical birth stratum); implied by HAVE_FIPS.
wolfssl/wolfcrypt/random.h: define WC_RNG_RBGC_STRATUM_IMMUTABLE whenever
HAVE_FIPS; declare wc_InitRngNonce_UserSeed().
wolfcrypt/src/random.c: in WC_RNG_RBGC_STRATUM_IMMUTABLE builds, inhibit all
post-init stratum relabeling, and refuse credited user-class reseeds
(wc_RNG_DRBG_Reseed(), wc_RNG_DRBG_Reseed_Nonce()) of instances below
WC_RNG_RBGC_USER_SEED_STRATUM with WRONG_TYPE_OBJECT_E, ahead of lock
acquisition and length handling, lest an instance carrying the vetted ESV
provenance marker have its reseed counter cleared by unvetted material --
the conformant aperture for user material is the uncredited
wc_RNG_DRBG_Stir(). add wc_InitRngNonce_UserSeed(), instantiating
born-user-seeded instances at WC_RNG_RBGC_USER_SEED_STRATUM, reseedable by
the user in all builds; a null seed returns BAD_FUNC_ARG, and in Hash_DRBG
builds an undersized seed (seedSz below WC_DRBG_SEED_SZ, zero included)
returns BAD_LENGTH_E, so that an argument mistake cannot mint a
primary-class instance the caller believes is user-seeded.
src/ssl.c, src/ssl_crypto.c: enable the modern RNG arms of
wolfSSL_FIPS_drbg_reseed(), wolfSSL_RAND_seed(), wolfSSL_RAND_poll(), and
wolfSSL_RAND_load_file() whenever WC_RNG_RBGC_STRATUM_IMMUTABLE, alongside
the incumbent ((!HAVE_FIPS || FIPS_VERSION3_GE(7,0,0)) && !HAVE_SELFTEST)
gate; rework the wolfSSL_RAND_poll() modern arm to
wc_RNG_DRBG_Reseed_Now(&globalRNG, NULL, 0), with the wolfCrypt return
mapped to WOLFSSL_SUCCESS/WOLFSSL_FAILURE; route wolfSSL_RAND_seed() and
wolfSSL_RAND_load_file() user material through the uncredited
wc_RNG_DRBG_Stir(), treating NOT_READY_E as success.
wolfcrypt/test/test.c: in rng_drbg_rbgc_test(), add born-user-seeded
instantiation coverage (sentinel stratum at init, user reseed accepted and
stratum retained, generation); in random_test(), add the
wc_InitRngNonce_UserSeed() error-contract battery (null/undersized
arguments, and NOT_COMPILED_IN without WC_RNG_HAVE_RBGC) and sub-sentinel
reseed-refusal probes on a plain instance (WRONG_TYPE_OBJECT_E, including
at zero length, with generation unaffected); pivot stratum expectations on
the new RBGC_RESEED_STRATUM() frozen/updated selector; in the threaded
reseed test, churn with credited wc_RNG_DRBG_Reseed_Now() where user-class
reseeds are refused; drive bank user-seeding through
WC_RNG_BANK_FLAG_STIR, treating NOT_READY_E as success.
tests/api/test_random.c: WC_RNG_RBGC_STRATUM_IMMUTABLE-arm expectations
(WRONG_TYPE_OBJECT_E) at the wc_RNG_DRBG_Reseed() probes in
test_wc_RNG_DRBG_Reseed() and test_wc_DrbgDecisionCoverage().
doc/dox_comments/header_files/random.h: add the wc_InitRngNonce_UserSeed()
entry; update wc_RNG_DRBG_Reseed()/wc_RNG_DRBG_Reseed_Nonce() for the
stratum-immutable refusal, the reseed-obligation BAD_LENGTH_E floor,
condemned-instance RNG_FAILURE_E, and the nonce null-with-nonzero-length
argument check; update wc_RNG_DRBG_GetRBGCStratum(), ReseedRBGC(), the
spawn family, and NextSeedGenerate_RBGC() for frozen-stratum semantics;
rebalance briefs to declarative one-liners with rationale consolidated
into \details.
falcon_native_verify_msg placed its 2*n word16 working buffer on the stack
unless WOLFSSL_SMALL_STACK was set. That buffer is sized for the highest
enabled level, so a Falcon-512 verify used 4 KB of stack for it whenever
Falcon-1024 was built in, and verify reached about 5.7 KB of stack on a
Cortex-M33/M7 while keygen and sign stay near 1.2-1.4 KB.
Verify now allocates the buffer per call, sized for the key's own level
(2 KB for Falcon-512, 4 KB for Falcon-1024), so it can return MEMORY_E;
the wc_falcon_verify_msg documentation says so. WOLFSSL_NO_MALLOC builds
keep the buffer on the stack. To keep it off the stack without an
allocator, WOLFSSL_FALCON_VERIFY_NO_MALLOC
(--enable-falcon=verify-no-malloc) holds the buffer in falcon_key
instead, sized for the highest enabled level, the same approach as
WOLFSSL_MLDSA_VERIFY_NO_MALLOC. With it, a key verifies one signature at
a time and falcon_key grows by 4*FALCON_MAX_N bytes.
On an STM32H743 a Falcon-512 verify goes from 5648 B of stack and no heap
to 1568 B of stack and 2048 B of heap, at the same speed. The peak stack
of a TLS 1.3 handshake that verifies a Falcon-512 chain drops from 6.3 KB
to 3.7 KB.
The falcon-interop backend matrix gains verify-no-malloc and
level1+dynamic-keys+verify-no-malloc legs running the full unit suite.
Local cap on the plaintext of each record sent, as in OpenSSL. Also
honour a negotiated max_fragment_length on DTLS writes and on
wolfSSL_write_dup() duplicates.