Skip to content

ISO 19790 Zeroization for v7 - #11434

Open
jackctj117 wants to merge 74 commits into
wolfSSL:masterfrom
jackctj117:ISO-19790-FIPS-PQ
Open

jackctj117 wants to merge 74 commits into
wolfSSL:masterfrom
jackctj117:ISO-19790-FIPS-PQ

Conversation

@jackctj117

@jackctj117 jackctj117 commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Zeroization of SSPs inside the FIPS v7 module for ISO/IEC 19790:2012 7.9.7: the Level 1 zeroization methods ([09.28], [09.29]) and the Level 2 rules for temporary SSPs ([09.30] to [09.32]). Every wipe is ForceZero; none is built out by a define. Scope is the v7 service list; see the end for what is left out on purpose.

By area

  • Hashes (sha.c, sha256.c, sha512.c, sha3.c, hmac.c): the per-block scratch of the C transforms lives in the context and is wiped once at Free (per-block and per-Final wipes cost 8 to 13 percent and were dropped). Free, a failed Copy and the GetHash copies wipe the whole context; WOLFSSL_HASH_KEEP messages and SMALL_STACK_CACHE schedules are wiped before they are freed. HMAC wipes its key padding temporaries and the object.
  • AES (aes.c, cmac.c): temporary key schedules, GHASH scratch through a caller-owned GcmScratch, keystream blocks, XTS tweaks and key-wrap intermediates. GCM and CCM decrypt output is wiped on authentication or primitive failure, the raw GHASH is wiped if E(J0) fails, a failed re-key clears the GCM state, and a stream whose primitive failed is invalidated. One-shot CMAC objects are wiped before free.
  • RSA (rsa.c): private components at Free and after a failed import or keygen, OAEP and padding temporaries, and the output of a failed encrypt or decrypt.
  • ECC and ECDSA (ecc.c): scalar copies, ladder temporaries and deterministic k; a rejected private import wipes k and clears the key type.
  • EdDSA (ed25519.c, ed448.c): scalar and nonce temporaries; the persistent hash is freed on failure.
  • ML-KEM (wc_mlkem.c, wc_mlkem_poly.c): the hash and PRF objects are wiped at every KeyGen, Encaps and Decaps exit (FIPS 203 3.3), with m, r, K', the implicit-rejection flag and the ciphertext of a failed encapsulation.
  • ML-DSA, SLH-DSA, LMS, XMSS: polynomial and seed temporaries; SLH-DSA copies aliased seed inputs before wiping the old key, clears its flags on failure and clears its reusable F output; the LMS and XMSS verify-path hash helpers wipe their state.
  • KDFs (kdf.c, pwdbased.c): SSH KDF, SRTP KDF, PBKDF2 and HKDF temporaries. The SSH KDF no longer re-initialises its hash inside the loop, which orphaned the cached schedule under SMALL_STACK_CACHE.
  • RNG (random.c, rng_bank.c): a failed instantiate is uninstantiated, the stir accumulator is wiped under a CONSUMING claim, seed buffers are wiped before they are freed, the NoiseSrc health scratch and the no-malloc DRBG state are wiped at Free.
  • misc.c: volatile fallback for ForceZero on compilers without the inline-asm barrier.

Out of scope on purpose, per the v7 service list: crypto callback and async paths, WC_C_DYNAMIC_FALLBACK, non-blocking contexts, SAKKE, DH, X25519/X448 and the fe/ge math files, PBKDF1, PKCS#12 PBKDF, scrypt, TLS 1.0 PRF, X9.63 KDF, ECIES, and the LMS/XMSS private-key paths. The SHA-3 block function signature change is carried through the fallback and FrodoKEM call sites so non-FIPS builds keep compiling. The SP backends and the assembly backends are a separate change and follow in a sister PR.

Performance: RSA-2048, ECDHE and ECDSA P-256/384/521 and ML-KEM-768 are within 1 percent of master on arm64.

ABI note

The per-block scratch of the software hashes now lives in the context so it can be wiped once at Free instead of once per block or per Final. That changes public struct sizes:

struct change
wc_Sha gains W[16]
wc_Sha256 gains Wbuf[64] (WC_SHA256_W_IN_CTX), except kernel builds (WC_SHA2_NO_SMALL_STACK) and small-stack builds with malloc, which keep it on the stack or heap
wc_Sha3 gains scratch[WC_SHA3_SCRATCH_W] (5, 30 or 80 lanes by configuration)
SlhDsaKey gains sha2Digest[32] at the end under WOLFSSL_SLHDSA_SHA2

Everything that embeds these grows with them: Hmac, wc_HashAlg and the OpenSSL compatibility holders. On the reviewer's 64-bit default build that was wc_Sha 104 to 168 bytes, wc_Sha256 120 to 376, wc_Sha3 416 to 656, Hmac 784 to 1024, wc_HashAlg 432 to 672; exact sizes depend on target and options. WC_SHA3_BLOCK_FN also takes a scratch argument when WC_SHA3_SCRATCH_W is defined.

Applications that embed these structs or link the shared library must be rebuilt against the new headers. Whether the release bumps the soname is a release decision. The layout conditions follow WOLFSSL_SMALL_STACK and WOLFSSL_SMALL_STACK_CACHE, so ecc.c now includes the hash, RNG and AES headers before its local override of those macros.

@jackctj117 jackctj117 self-assigned this Sep 10, 2026
@jackctj117
jackctj117 requested review from kaleb-himes and a lite review from Copilot September 10, 2026 22:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved zeroization gaps remain, along with a critical SHA-3 build-compatibility issue.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This pull request expands secure zeroization across wolfCrypt cryptographic implementations to support ISO/IEC 19790 compliance.

Changes:

  • Clears sensitive keys, hashes, seeds, signatures, and temporary buffers.
  • Adds cleanup across classical, elliptic-curve, post-quantum, hashing, RNG, and KDF code.
  • Extends zeroizing cleanup to architecture-specific arithmetic implementations.
File summaries
File Changes
wolfcrypt/src/wolfentropy.c Zeroizes entropy samples and conditioning state.
wolfcrypt/src/wc_xmss.c Clears XMSS state, seeds, and private material.
wolfcrypt/src/wc_xmss_impl.c Clears XMSS hash intermediates.
wolfcrypt/src/wc_slhdsa.c Clears SLH-DSA signing and key-generation state.
wolfcrypt/src/wc_mlkem.c Clears ML-KEM key-generation failure state.
wolfcrypt/src/wc_mldsa.c Clears ML-DSA signing and private state.
wolfcrypt/src/wc_lms_impl.c Clears LMS hash and derived-seed buffers.
wolfcrypt/src/sp_x86_64.c Zeroizes x86-64 arithmetic temporaries.
wolfcrypt/src/sp_riscv64.c Zeroizes RISC-V arithmetic temporaries.
wolfcrypt/src/sp_cortexm.c Zeroizes Cortex-M arithmetic temporaries.
wolfcrypt/src/sp_c64.c Zeroizes generic 64-bit arithmetic temporaries.
wolfcrypt/src/sp_c32.c Zeroizes generic 32-bit arithmetic temporaries.
wolfcrypt/src/sp_armthumb.c Zeroizes ARM Thumb arithmetic temporaries.
wolfcrypt/src/sp_arm64.c Zeroizes ARM64 arithmetic temporaries.
wolfcrypt/src/sp_arm32.c Zeroizes ARM arithmetic temporaries.
wolfcrypt/src/sha512.c Clears SHA-512 temporary state.
wolfcrypt/src/sha3.c Clears SHA-3 state and buffers.
wolfcrypt/src/sha256.c Clears SHA-256 temporary state.
wolfcrypt/src/sha.c Clears hash temporary state.
wolfcrypt/src/rsa.c Clears RSA temporary values.
wolfcrypt/src/random.c Clears DRBG and temporary state.
wolfcrypt/src/pwdbased.c Clears password-based derivation temporaries.
wolfcrypt/src/kdf.c Clears derived-key temporary buffers.
wolfcrypt/src/hmac.c Clears HMAC temporary state.
wolfcrypt/src/ed448.c Clears Ed448 signing intermediates.
wolfcrypt/src/ed25519.c Clears Ed25519 signing intermediates.
wolfcrypt/src/ecc.c Clears ECC signing and scalar temporaries.
wolfcrypt/src/dh.c Clears DH private and temporary state.
wolfcrypt/src/curve448.c Clears Curve448 private material.
wolfcrypt/src/curve25519.c Clears Curve25519 private material.
wolfcrypt/src/cmac.c Clears CMAC state before release.
wolfcrypt/src/aes.c Clears AES temporary state and intermediates.
Review details

Suppressed comments (16)

wolfcrypt/src/aes.c:18629

  • This wrapper now clears its tweak buffer, but AesXtsEncryptUpdate_sw still leaves its local buf/ciphertext-stealing buffers uncleared when AES fails (and after normal return). Those buffers contain plaintext or key-derived intermediate data, so the inner helper needs success and error cleanup as well.
    ForceZero(tweak_block, sizeof(tweak_block));

wolfcrypt/src/aes.c:19177

  • Clearing tweak_block here does not clear the local buf and tmp2 intermediates in AesXtsDecryptUpdate_sw. AES failures and the bounds-error return leave ciphertext/plaintext-derived values on the stack; add cleanup in that helper for each exit path.
    ForceZero(tweak_block, sizeof(tweak_block));

wolfcrypt/src/cmac.c:681

  • wc_AesCmacVerify_ex uses local a for the computed tag, but wiping the Cmac object here does not touch that buffer. On successful comparison, mismatch, or generation error, the derived tag remains on the stack; add a cleanup of a on every exit after it is declared.
    ForceZero(cmac, sizeof(Cmac));
    XFREE(cmac, NULL, DYNAMIC_TYPE_CMAC);

wolfcrypt/src/curve25519.c:712

  • This handles a public-generation failure, but wc_curve25519_make_priv() can partially fill key->k and return an RNG error before this block is entered. Because the function then returns with privSet clear, the partial scalar is left in the key object. Wipe key->k on every nonzero private-generation result, not only after public derivation fails.
            ForceZero(key->k, sizeof(key->k));
            key->privSet = 0;
        }

wolfcrypt/src/curve25519.c:805

  • The normal key-generation path has the same gap as the nonblocking path: wc_curve25519_make_priv() may leave partial RNG output in key->k, then the function returns before this public-generation cleanup. Clear the scalar when private generation fails as well.
                /* Public half failed: drop the scalar too
                 * (ISO/IEC 19790:2012 7.9.7). */
                ForceZero(key->k, sizeof(key->k));
                key->privSet = 0;

wolfcrypt/src/curve448.c:296

  • This clears the scalar only when public derivation fails. wc_RNG_GenerateBlock() can partially fill key->k before returning an error, and that path bypasses this block, leaving failed key material in the object. Wipe key->k for RNG failures too before returning.
            ForceZero(key->k, sizeof(key->k));
            XMEMSET(key->p, 0, sizeof(key->p));
            /* A zeroised SSP shall not be reusable
             * (ISO/IEC 19790:2012 7.9.7 [09.29]). */
            key->privSet = 0;

wolfcrypt/src/dh.c:1515

  • GeneratePrivateDh186() can fill part of priv and then return an error from mp_to_unsigned_bin() after setting *privSz, but this flag is set only when the call returns success. The new cleanup therefore skips a partially written private key on that path; track possible output (or wipe on every nonzero return using the output capacity) before returning.
    privWritten = (ret == 0);

wolfcrypt/src/dh.c:1528

  • The new private-key wipe is limited to wc_DhGenerateKeyPair_Sync. The Intel async path generates priv before bignum conversion and hardware submission, then returns directly on failures without clearing it or resetting *privSz; a failed asynchronous key pair can therefore expose the generated private value. Apply the same failure cleanup to that path before its return.
    if (privWritten && (ret != 0)) {
        /* A failed pair is not handed back (ISO/IEC 19790:2012 7.9.7). */
        ForceZero(priv, *privSz);
        *privSz = 0;

wolfcrypt/src/ecc.c:7904

  • The cleanup at this point only runs on the normal software path. Deterministic signing has already populated key->sign_k, but the allocation/initialization failures in the earlier bignum setup and the Intel/Cavium async branch return before reaching this line, leaving the nonce allocated in the key. Route those exits through a common cleanup label (while preserving the pending-operation case) so every completed error consumes sign_k.
   ecc_sign_k_forcezero(key);

wolfcrypt/src/ed25519.c:485

  • The PCT cleanup clears the generated key, but ed25519_pairwise_consistency_test() also leaves its RNG-generated digest message on the stack on success and every failure path. Since this temporary random material is created solely for the consistency test, clear it before returning from the PCT as well.
        ForceZero(key->k, ED25519_PRV_KEY_SIZE);
        ForceZero(key->p, ED25519_PUB_KEY_SIZE);

wolfcrypt/src/ed448.c:417

  • The PCT cleanup clears the generated key, but ed448_pairwise_consistency_test() also leaves its RNG-generated digest message on the stack on success and every failure path. Clear that temporary random material before the PCT returns.
            ForceZero(key->k, ED448_PRV_KEY_SIZE);
            ForceZero(key->p, ED448_PUB_KEY_SIZE);

wolfcrypt/src/wc_lms_impl.c:485

  • wc_Sha256HashBlock() stages the input block in sha256->buffer on the byte-reversing path and does not clear it. The LMS caller passes blocks containing SEED/WOTS material, so clearing only the local digest output still leaves secret input in the hash object after this helper returns.
    /* Prefix is x_q[i] or a child SEED (ISO/IEC 19790:2012 7.9.7). */
    ForceZero(output, sizeof(output));

wolfcrypt/src/wc_lms_impl.c:550

  • In the 64-to-120-byte path above, the secret suffix and padding are written into sha256->buffer and wc_Sha256HashBlock() leaves that buffer populated. The new output wipe does not remove this residual seed material; clear the SHA-256 buffer here as well.
    /* Prefix is x_q[i] or a child SEED (ISO/IEC 19790:2012 7.9.7). */
    ForceZero(output, sizeof(output));

wolfcrypt/src/wc_lms_impl.c:596

  • This manual finalizer constructs the padded block directly in sha256->buffer, and wc_Sha256HashBlock() does not reset or wipe that field. Consequently, secret LMS prefixes can remain in the hash object after the finalizer returns; wipe the buffer on both success and error paths.
    /* Prefix is x_q[i] or a child SEED (ISO/IEC 19790:2012 7.9.7). */
    ForceZero(output, sizeof(output));

wolfcrypt/src/wc_slhdsa.c:7368

  • On a hypertree-signing failure, the code clears only the FORS prefix. slhdsakey_ht_sign can have already written part of the suffix, and the caller has also written the initial randomness prefix, so those regions can retain partial signature/WOTS intermediates after an error. Clear the entire produced signature buffer on failure, with the outer caller handling its prefix length correctly.
    if (ret != 0) {
        /* Unreleased FORS secrets may be in sig
         * (ISO/IEC 19790:2012 7.9.7). */
        ForceZero(sigFors, key->params->k * (1 + key->params->a) * n);

wolfcrypt/src/wc_slhdsa.c:7240

  • After callback unavailability, the software fallback stages all three seeds in key->sk and slhdsakey_compute_root() can return an error. The callback-only wipe above is not reached on that path, so failed software generation leaves private seeds marked in the key object; clear them and invalidate the key before returning the fallback error.
            /* fall-through when unavailable */
            ret = 0;
  • Files reviewed: 32/32 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread wolfcrypt/src/sha3.c Outdated
Comment thread wolfcrypt/src/aes.c Outdated
@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

gcc-arm-cortex-m0plus

  • FLASH: .text +152 B (+0.2%, 70,611 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m3

  • FLASH: .text +380 B (+0.3%, 131,149 B / 262,144 B, total: 50% used)

gcc-arm-cortex-m4

  • FLASH: .text +704 B (+0.3%, 210,652 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +256 B (+0.4%, 73,271 B / 262,144 B, total: 28% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +1,664 B (+0.9%, 184,255 B / 262,144 B, total: 70% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +320 B (+0.2%, 194,648 B / 1,048,576 B, total: 19% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +256 B (+0.4%, 67,929 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +2,048 B (+0.3%, 798,188 B / 1,048,576 B, total: 76% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +1,792 B (+0.8%, 225,344 B / 262,144 B, total: 86% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +2,176 B (+0.7%, 313,892 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +1,536 B (+0.4%, 344,664 B / 1,048,576 B, total: 33% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +256 B (+0.4%, 67,929 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +384 B (+0.3%, 131,921 B / 262,144 B, total: 50% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +1,664 B (+0.7%, 250,894 B / 262,144 B, total: 96% used)

gcc-arm-cortex-m7

  • FLASH: .text +704 B (+0.3%, 210,652 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +2,112 B (+0.7%, 314,788 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +1,600 B (+0.6%, 250,894 B / 262,144 B, total: 96% used)

stm32-sim-stm32h753

  • FLASH: .text +1,472 B (+0.7%, 207,192 B / 2,097,152 B, total: 10% used)

zephyr-frdm_rw612

  • FLASH: log_const_area -8 B, rodata -8 B, text +1,424 B (+0.5%, 283,872 B / 67,108,864 B, total: 0% used)

Catch memory regressions in CI with MemBrowse. Free for open source.

@jackctj117
jackctj117 force-pushed the ISO-19790-FIPS-PQ branch 2 times, most recently from a21ae27 to 0ad5d42 Compare September 15, 2026 19:21
@jackctj117 jackctj117 added the For FIPS v7 Module Related to FIPS v7.0.0 module prep for submission label Sep 29, 2026
Comment thread wolfcrypt/test/test.c Outdated

@JacobBarthelmeh JacobBarthelmeh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good. Performance is under review.

jackctj117 and others added 30 commits October 28, 2026 11:23
Callback and async paths, dynamic fallback, non-blocking contexts,
SAKKE, DH, X25519/X448 and the fe/ge arithmetic, PBKDF1, PKCS#12,
scrypt, TLS 1.0 PRF, X9.63 KDF, ECIES and the LMS/XMSS private paths.
They return in a non-FIPS follow-up.
ML-KEM wipes its hash objects at every exit (FIPS 203 3.3). GCM wipes
the raw GHASH, the stale H table, the stream state and partial plaintext
on a primitive failure. OAEP unpad wipes its whole scratch. The ECC
ladder tmp is wiped on every exit and a rejected private import clears
the key type. EdDSA frees the persistent hash on failure. The stir purge
keeps the old EMPTY store when its claim fails. ecc.c reads the shared
struct layouts before its local small-stack override.
The ECC import type check is skipped on the device set-key path: with
WOLF_CRYPTO_CB_SETKEY and WOLF_CRYPTO_CB_FIND the import is staged in a
temporary key, so a rejected scalar leaves the caller's key as it was.
The RSA OAEP and AES-GCM failure-wipe tests are skipped under
HAVE_SELFTEST, where the CAVP build replaces rsa.c and aes.c with the
tagged selftest sources.
Importing over a live key dropped the previous scalar and ordinates
without a wipe or a free. Caught by the async valgrind run.
wolfBoot's keytools imports into a key that was never initialised, so
the cleanup from 63d8a61 read garbage and crashed. The test now
rejects the scalar into a fresh key instead of re-importing over a
live one.
Keeping the C round scratch in the context cost 1 to 3 percent on the C lane (SHA-3, SHAKE, KMAC, ML-KEM). The block function and its callers go back to master's local scratch; the state wipes at Free stay.
… per block

Move W back to the stack as on master. Holding it in the context made the
C transform alias W with the input block, which cost SHA-256 and HMAC-SHA256
about 3% on default builds. W is now wiped before the transform returns, the
same way sha512.c already handles its schedule, so a keyed block never
outlives the frame. The Transform_Sha256_Len copy buffer is hoisted out of
the loop and wiped once per call. WOLFSSL_CHECK_MEM_ZERO covers all of them.
…olatile stores

Without GNU inline asm, or with WOLFSSL_NO_ASM, WC_BARRIER_DATA() used to
fall back to WC_BARRIER(), which does not cover a local whose address never
escaped, so the wipe could be dropped as a dead store. The volatile typedefs
in ForceZero worked around that at the cost of a byte-by-byte wipe and two
fences per call. The fallback now passes the pointer to wc_BarrierDataSink()
in wc_port.c, reached through a volatile load of a function pointer so link
time optimization cannot see the empty body, and ForceZero goes back to the
plain stores on master.
gcc with -Wsign-conversion rejects the int product of the byte parameters
being passed as a size_t.
…plete zeroization coverage in wc_RNG_TestSeed() and _InitRng().

* NextStirPurge(): never claim or repaint a constructor's _TORNDOWN --
  the reopen is the constructor's to make.  The claim loop skips
  _TORNDOWN alongside _CONSUMING (re-checked on every retry), and the
  reopen is now a CAS retry loop rather than a plain store, so a
  depositor's fill length stored over the claim is discarded (length
  only -- its bytes lie in place until the next wipe or full-buffer
  fold, benign for the uncredited stir) while a _TORNDOWN painted at any
  point is left standing.  Both abort arms (a port's retry clause) get
  explicit dispositions: an aborted claim now stores nothing, since any
  store could repaint a live claim or a _TORNDOWN, and the stale fill's
  resurrection is benign by the three-no-ops doctrine; an aborted reopen
  stores EMPTY, since stranding _CONSUMING would silence the stir
  aperture on the instance, and the repaint risk on that already-exotic
  path is accepted.

* wc_RNG_TestSeed(): zeroize the APT reference byte and the all-values
  test's majority candidate at the end of their window scopes, matching
  the RCT arm's prevByte -- all three hold a byte of raw seed.

* _InitRng(): set drbg_scratch_instantiated before the scratch
  instantiate calls (both boundaries), as for drbg_instantiated: a
  failed instantiate still leaves V in the state, and the failure arm
  must uninstantiate it.  The scratch is NULL-seeded, so the stranded
  state would be a public constant -- wiped anyway, for uniformity of
  the invariant.
Follows the WC_INLINE pattern: C99 restrict where available, __restrict on
GNU-compatible compilers and MSVC (including C++ translation units), and nothing
on compilers with no support.

WC_RESTRICT applies the C99 restrict contract: it tells the compiler that in
each call, objects modified through the qualified pointer are accessed only
through it (or through pointers derived from it), and that objects it accesses
are modified only through the qualified pointer.  A caller-supplied buffer
therefore compiles like a local array.

The practical benefit is that, after a store through the qualified pointer, the
compiler can keep using a value previously loaded through an unrelated pointer
without reloading it.
wc_mldsa.c mldsa_import_priv_raw: the pre-copy ForceZero of key->k runs
before XMEMCPY(key->k, priv, privSz), so a caller re-importing from
key->k itself (an exact alias) has the source zeroed and imports an
all-zero key -- and because mldsa_check_eta_range() validates priv
BEFORE the wipe, the zeroed import passes validation, rebuilds the
caches from zeros, and returns success with prvKeySet set.

Skip both the wipe and the copy for an exact alias: the new key is
already in place, and exact-alias XMEMCPY is undefined besides.  The
sibling import sites (SLH-DSA seed and encoded imports, ML-DSA keygen's
k move) preserve aliased input in a stack bounce buffer per ISO 19790
7.9.7; at ML-DSA private-key sizes (2560-4896 bytes) a stack bounce is
not an option, and in the non-aliased case the same-size overwrite
leaves no residue of the old key, so the explicit wipe is kept only on
that path.
Refactor to eliminate the per-block ForceZero of the C transforms' message
schedule (and, for SHA-256, the working variables):  Move the scratch to the
caller so the wipe runs once per public call instead of once per block, without
changing any context layout, public signature, dispatch arm, or function-pointer
table:

 - Sha256TScratch{W,S} / Sha512TScratch{W}, provisioned by per-config
   DECL/BEGIN/WIPE macros built on the WC_DECLARE_VAR / WC_ALLOC_VAR_EX /
   WC_FREE_VAR_EX family: a local in big-stack builds, a per-call heap block
   under WOLFSSL_SMALL_STACK (previously allocated and freed PER BLOCK), and the
   per-context cache under WOLFSSL_SMALL_STACK_CACHE, which keeps its zero
   per-call cost and wipe-at-Free semantics (cache allocations are resized to
   the scratch struct; the wc_Sha512Transform() backup block now sits after it).

 - External providers of wc_Sha256.W / wc_Sha512.W (linuxkm's w_buf, the
   Nuvoton copy callback, mpfs_athena's SHA-384 copy) size their buffers by new
   WC_SHA256_TSCRATCH_SIZE / WC_SHA512_TSCRATCH_SIZE constants in the public
   headers, and each translation unit statically asserts its scratch struct
   matches its constant, so providers and the transform cannot drift apart.

 - Transform_Sha256_scr() / _Transform_Sha512_scr() take the scratch as a
   WC_RESTRICT parameter, so values loaded through other pointers stay in
   registers across schedule stores, avoiding aliasing cost otherwise associated
   with placing W on the stack.  The old-signature transforms remain as
   compatibility shims with per-call scratch, keeping every dispatch arm and
   function-pointer type untouched.

 - The bulk C paths hoist one scratch across their block loop:
   Transform_Sha256_Len(), both *_Len_C_from_raw(), and both of Sha512Update()'s
   block loops -- including the intel arm's runtime pre-AVX fallback loop -- so
   every multi-block C path wipes once per call.  In the loop shared with the
   ESP32 port, the hoist is compiled only for the software-transform
   configuration; hardware-port arms are untouched.

 - The *_C_from_raw() adapters become thin wrappers over new *_C_from_raw_scr()
   variants: the block is staged (when not already there) in the aligned context
   buffer and byte-reversed in place, so the bulk loops share one scratch while
   raw input of any alignment stays safe on strict-alignment targets.

 - SHA-512's working variables T stay a local with their existing
   per-block wipe: behind a pointer they cannot be register-allocated
   across the 80 rounds, measured at ~9% on the C transform, while their
   wipe is one cache line.

 - WOLFSSL_CHECK_MEM_ZERO coverage moves with the scratch: registered at
   BEGIN, checked at WIPE, with the scratch zeroed at registration (a const
   void* registration argument pointing at an uninitialized object trips
   -Wmaybe-uninitialized under -Werror); wc_Sha512Transform()'s existing
   backup-buffer registration gets the same initialization.

Zeroization lifetime: transient schedule and working variables are wiped
when the public call returns; the pending input block in ctx->buffer
already persists longer, so no new exposure class is introduced.  The
chaining state the working variables derive from persists in the context
until Free, as before.

Performance scope (noasm C transforms; ForceZero never auto-vectorizes, so the
wipe is scalar word stores in userspace and kernel alike): recovers the ~2.3%
SHA-256 regression from the per-block W+S wipes, and improves SHA-512 8% over
master itself by amortizing the per-block W wipe out of Sha512Update()'s block
loop.  Asm-accelerated paths (intelasm, armasm) carry their own schedules and
are untouched in both directions.
…IC_STRUCT_INITIALIZER, not ad hoc { 0 }, for warning-free build in C++ mode.
…ro stack buffers before WOLFSSL_CHECK_MEM_ZERO registration.

wc_MemZero_Add() takes const void*, so gcc assumes a read: registering a
not-yet-written stack buffer trips -Wmaybe-uninitialized under -Werror.  Zero
each registered buffer first -- cost confined to WOLFSSL_CHECK_MEM_ZERO builds.
…6TScratch), as allocation operand in smallstack wc_Sha{224,256}Copy().

wolfssl/wolfcrypt/sha{256,512}.h: add note that WC_SHA{256,512}_TSCRATCH_SIZE are precious names that wolfBoot depends on.
…frodokem_mat.c: refactor ad-hoc __restrict macros using WC_RESTRICT (no functional changes).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For FIPS v7 Module Related to FIPS v7.0.0 module prep for submission

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants