ISO 19790 Zeroization for v7 - #11434
Conversation
There was a problem hiding this comment.
🟡 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_swstill leaves its localbuf/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_blockhere does not clear the localbufandtmp2intermediates inAesXtsDecryptUpdate_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_exuses localafor the computed tag, but wiping theCmacobject here does not touch that buffer. On successful comparison, mismatch, or generation error, the derived tag remains on the stack; add a cleanup ofaon 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 fillkey->kand return an RNG error before this block is entered. Because the function then returns withprivSetclear, the partial scalar is left in the key object. Wipekey->kon 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 inkey->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 fillkey->kbefore returning an error, and that path bypasses this block, leaving failed key material in the object. Wipekey->kfor 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 ofprivand then return an error frommp_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 generatesprivbefore 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 consumessign_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-generateddigestmessage 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-generateddigestmessage 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 insha256->bufferon the byte-reversing path and does not clear it. The LMS caller passes blocks containingSEED/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->bufferandwc_Sha256HashBlock()leaves that buffer populated. The newoutputwipe 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, andwc_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_signcan 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->skandslhdsakey_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.
| ForceZero(sha3->s, sizeof(sha3->s)); | ||
| ForceZero(sha3->t, sizeof(sha3->t)); |
| } | ||
| /* XOR plain text into encrypted counter into cipher text buffer. */ | ||
| xorbufout(out, scratch, in, WC_AES_BLOCK_SIZE); | ||
| ForceZero(scratch, sizeof(scratch)); |
|
9df8962 to
86ed158
Compare
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.
86ed158 to
a21ae27
Compare
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):
ForceZerocalls 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):
Cmacstructure is securely zeroed before being freed in both generate and verify functions. [1] [2]Curve25519/Curve448 (wolfcrypt/src/curve25519.c, wolfcrypt/src/curve448.c):
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):
ForceZeroto 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):
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.