Skip to content

ISO 19790 Zeroization for v7 - #11434

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

ISO 19790 Zeroization for v7#11434
jackctj117 wants to merge 8 commits into
wolfSSL:masterfrom
jackctj117:ISO-19790-FIPS-PQ

Conversation

@jackctj117

Copy link
Copy Markdown
Contributor

This pull request makes extensive improvements to sensitive data handling across cryptographic modules by ensuring that secret or temporary buffers are securely zeroed out after use, in compliance with ISO/IEC 19790:2012 section 7.9.7. The changes cover AES, CMAC, Curve25519/448, DH, and ECC implementations. The main goal is to prevent sensitive information from lingering in memory, reducing the risk of leakage or unintended reuse.

Sensitive Data Zeroization Enhancements:

AES (wolfcrypt/src/aes.c):

  • Added ForceZero calls to securely erase temporary key schedules, GHASH buffers, scratch buffers, tweak blocks, and key wrap/unwrap intermediates after use or on error. This includes zeroing out outputs on decryption failures and during AES-GCM and XTS operations. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16]

CMAC (wolfcrypt/src/cmac.c):

  • Ensured that the Cmac structure is securely zeroed before being freed in both generate and verify functions. [1] [2]

Curve25519/Curve448 (wolfcrypt/src/curve25519.c, wolfcrypt/src/curve448.c):

  • Zeroized private scalars (key->k) and context structures on key generation failure or context replacement, and ensured private key material is not reusable after failure. Zeroed non-blocking context and key structures on free. [1] [2] [3] [4]

Diffie-Hellman (wolfcrypt/src/dh.c):

  • Used ForceZero to clear non-blocking context structures, private values on error, and ensured that imported private keys do not overwrite existing secrets without zeroization. [1] [2] [3] [4] [5]

ECC (wolfcrypt/src/ecc.c):

  • Added zeroization for temporary scalar values, nonces, and ephemeral keys used in ECDSA signing and deterministic k generation, including special handling for single-precision and heap-allocated variants. [1] [2] [3] [4] [5] [6]

These changes collectively improve the security posture of the cryptographic library by ensuring sensitive data is wiped from memory as soon as it is no longer needed.

@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 on lines +1602 to +1603
ForceZero(sha3->s, sizeof(sha3->s));
ForceZero(sha3->t, sizeof(sha3->t));
Comment thread wolfcrypt/src/aes.c Outdated
}
/* XOR plain text into encrypted counter into cipher text buffer. */
xorbufout(out, scratch, in, WC_AES_BLOCK_SIZE);
ForceZero(scratch, sizeof(scratch));
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m0plus

  • FLASH: .text +152 B (+0.2%, 67,111 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m3

  • FLASH: .text +156 B (+0.1%, 125,931 B / 262,144 B, total: 48% used)

gcc-arm-cortex-m4

  • FLASH: .text +256 B (+0.1%, 205,213 B / 262,144 B, total: 78% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +128 B (+0.2%, 69,603 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +256 B (+0.1%, 179,160 B / 262,144 B, total: 68% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +128 B (+0.1%, 187,324 B / 1,048,576 B, total: 18% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +128 B (+0.2%, 64,453 B / 262,144 B, total: 25% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +576 B (+0.1%, 784,420 B / 1,048,576 B, total: 75% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +448 B (+0.2%, 218,076 B / 262,144 B, total: 83% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +512 B (+0.2%, 302,600 B / 1,048,576 B, total: 29% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +192 B (+0.1%, 333,088 B / 1,048,576 B, total: 32% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +128 B (+0.2%, 64,453 B / 262,144 B, total: 25% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +128 B (+0.1%, 126,707 B / 262,144 B, total: 48% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +256 B (+0.1%, 242,895 B / 262,144 B, total: 93% used)

gcc-arm-cortex-m7

  • FLASH: .text +256 B (+0.1%, 205,213 B / 262,144 B, total: 78% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +512 B (+0.2%, 303,560 B / 1,048,576 B, total: 29% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +320 B (+0.1%, 242,959 B / 262,144 B, total: 93% used)

linuxkm-standard

  • Data: __patchable_function_entries -16 B (-0.0%, 49,856 B)

stm32-sim-stm32h753

  • FLASH: .text +1,792 B (+1.0%, 190,396 B / 2,097,152 B, total: 9% used)
    No memory changes detected for:
  • linuxkm-pie

ISO/IEC 19790:2012 7.9.7 and FIPS 186-5 7.7 step 1.
wc_Sha3Free and wc_ShaFree now clear their state; also DRBG, RSA MGF1, AES KW, XMSS, LMS, SLH-DSA, ML-KEM and X25519/X448 paths.
SP_FREE_VAR left k, priv, point, b and m in place. The generator needs the same change.
Hash Free callback returns, GetHash copies, RSA/ECC/DH/EdDSA/PQC error paths, AES KW/XTS/GCM scratch.
…py include

wc_curve25519_free and wc_FreeDhKey no longer wipe the non-blocking
context (TLS frees it first); wolfentropy.c pulls in misc.c; GCM
scratch wiped once per call; KW wipe only after out is written;
wc_AesFree releases streamData before the wipe; test_digest.h freed
src instead of dst.
XMEMSET is a dead store the compiler may drop. Covers HMAC and SHA-2 Copy
failure paths, X25519/ECC nonblocking contexts, ECC keyRaw, and CCM
plaintext on tag mismatch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants