AES reject-no-key guard and RSA-PSS hash-binding test (F-6151, F-6157)#10762
AES reject-no-key guard and RSA-PSS hash-binding test (F-6151, F-6157)#10762julek-wolfssl wants to merge 15 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces two security hardening changes in wolfCrypt: (1) prevent AES mode operations from running after wc_AesInit() but before a key is set, and (2) strengthen RSA-PSS regression coverage to ensure signatures are bound to the message digest.
Changes:
- Add
aes->keylen == 0rejection guards to multiple AES mode implementations (CBC/CTR/GCM/ECB/CFB/OFB) inwolfcrypt/src/aes.c. - Add a new
aes_no_key_set_test()and run it fromaes_test()to ensure AES mode APIs reject use beforewc_AesSetKey(). - Extend
rsa_pss_test()to tamper the digest post sign/verify and requireBAD_PADDING_E, making the hash-compare step decisive.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| wolfcrypt/src/aes.c | Adds “key not set” guards to prevent AES modes from operating with an uninitialized (zero) key schedule in the software paths. |
| wolfcrypt/test/test.c | Adds AES regression test for “no key set” behavior and adds RSA-PSS digest-tampering negative verification to ensure hash binding. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
retest this please |
|
|
@julek-wolfssl please resolve conflicts with master |
8460187 to
ef13b58
Compare
Frauschi
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: APPROVE
Findings: 3 total — 3 posted, 1 skipped
Posted findings
- [Medium] AES-CCM not covered by the keyInstalled guard (same zero-key exposure as GCM) —
wolfcrypt/src/aes.c:14403 - [Low] New Aes struct member inserted mid-struct rather than appended at the end —
wolfssl/wolfcrypt/aes.h:300 - [Info] sz==0 early-return precedes keyInstalled check in some mode paths (inconsistent no-key result) —
wolfcrypt/src/aes.c:7001, 15568, 15656, 16113
Skipped findings
- [Info] sz==0 early-return precedes keyInstalled check in some mode paths (inconsistent no-key result)
Review generated by Skoll via Claude/Codex
Frauschi
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: REQUEST_CHANGES
Findings: 1 total — 1 posted, 0 skipped
Posted findings
- [High] keyInstalled guard breaks AES modes for ports that supply their own wc_AesSetKey —
wolfcrypt/src/aes.c:10867
Review generated by Skoll via Claude/Codex
5ff58d0 to
dd065cb
Compare
Frauschi
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: APPROVE
Findings: 4 total — 2 posted, 2 skipped
Posted findings
- [Medium] WOLFSSL_SECO_CAAM key-set path returns success without setting keyInstalled —
wolfcrypt/src/aes.c:5560-5566 - [Medium] aes_no_key_set_test may fail under WOLF_CRYPTO_CB / device builds —
wolfcrypt/test/test.c:16277-16281
Skipped findings
- [Low] New keyInstalled bitfield inserted mid-struct rather than appended
- [Info] Duplicated keyInstalled guard block repeated across many mode entry points
Review generated by Skoll via Claude/Codex
Frauschi
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: REQUEST_CHANGES
Findings: 3 total — 1 posted, 3 skipped
Posted findings
- [High] FREESCALE_MMCAU / FREESCALE_LTC wc_AesSetKeyLocal never sets keyInstalled, breaking guarded modes —
wolfcrypt/src/aes.c:5062 (FREESCALE_MMCAU), wolfcrypt/src/aes.c:4600 (FREESCALE_LTC)
Skipped findings
- [Medium] New keyInstalled bitfield inserted mid-struct breaks ABI and packs poorly
- [Low] Inconsistent sz==0 handling between generic and PIC32 CBC guards
- [High] FREESCALE_MMCAU / FREESCALE_LTC wc_AesSetKeyLocal never sets keyInstalled, breaking guarded modes
Review generated by Skoll via Claude/Codex
|
retest this please |
da26195 to
23363d7
Compare
wc_AesInit zeroes the Aes struct, leaving keylen and rounds at 0. The CTR/CBC/GCM/ECB/CFB/OFB entry points never verified that a key had been installed with wc_AesSetKey, so calling them right after wc_AesInit ran the software cipher against an all-zero key schedule and returned success instead of an error. For CTR this exposes a reconstructable keystream; for GCM the hash subkey H is also zero, making the tag forgeable. Add the keylen == 0 guard already used by wc_AesXtsEncrypt to the software mode paths. The check is placed after the crypto callback fall-through so device-managed keys are unaffected. Hardware paths that call wc_AesGetKeySize already reject this case via rounds == 0. Add aes_no_key_set_test(), run from aes_test(), which inits an Aes context and confirms every mode rejects use before a key is set.
wc_RsaPSS_CheckPadding_ex2 binds a PSS signature to the message with a single comparison of the recomputed hash against the encoded hash H. Every positive PSS test passes the correct digest and the negative tests use a wrong salt length that fails before that comparison is reached, so deleting the comparison left all default tests passing even though any well-formed PSS structure would then verify against any message. After each successful sign/verify round-trip in rsa_pss_test, flip one bit of the message digest, keep the correct salt length, and require BAD_PADDING_E. This makes the hash comparison the deciding check and catches its removal.
Replace the keylen == 0 guards with a new Aes.keySet bitfield, set by the key-installation paths and checked in the mode APIs. keylen alone is not a reliable "key installed" signal across backends: some ports map a zero key length to a default (e.g. PSOC6 maps keylen 0 to AES-128), so a keylen-based check placed only in the software paths left those builds running with the all-zero key schedule and would fail aes_no_key_set_test. keySet is set in wc_AesSetKeyLocal (the funnel for the software SetKey/SetKeyDirect/GcmSetKey paths) and in the PSOC6 wc_AesSetKey wrapper, and checked in the public CBC/CTR/GCM/ECB/CFB/OFB entry points, including the PSOC6 port variants.
Address review of the keySet bitfield: - Rename keySet to keyInstalled. struct Aes already has a Cavium-only keySet member (HAVE_CAVIUM_OCTEON_SYNC), so an unconditional keySet bitfield was a duplicate that would not compile on that build. - Set keyInstalled on every key install, not just the software funnel. The ARM, PPC64 and other hardware wc_AesSetKey/SetKeyDirect variants populate keylen/rounds directly; without also setting the flag they would have made the shared mode APIs reject a validly-keyed context. The flag is now set at every keylen-assignment point in aes.c and in the PSOC6 SetKey wrapper.
aes_no_key_set_test runs on every CI backend and exposed two ports whose own mode functions ran with an unset key. Other hardware backends (STM32, the secure-element sims) already reject this via wc_AesGetKeySize. - PIC32MZ: guard wc_AesCbcEncrypt/Decrypt. keyInstalled is already set through wc_AesSetKeyLocal, the generic SetKey funnel these use. - RISC-V assembly: set keyInstalled in all three wc_AesSetKey variants and guard CBC/ECB/GCM encrypt and decrypt. CTR already rejects via its rounds switch.
Under FIPS/selftest the AES functions come from the validated module, which does not reject use before a key is installed, so aes_no_key_set_test failed on the CAVP selftest and FIPS customer-config CI jobs. The keyInstalled guard is a non-FIPS hardening, so gate the test on !HAVE_FIPS && !HAVE_SELFTEST. The FIPS CASTs (wc_RunAllCast_fips) pass, confirming the change does not affect validated AES.
Address review feedback on the negative tampered-digest assertion, which encoded ret via WC_TEST_RET_ENC_EC(ret). That is in fact non-zero even for ret == 0, but switch to WC_TEST_RET_ENC_NC so the failure code is unambiguously non-zero and independent of ret. Verified by mutation: deleting the signature-to-message hash compare still fails the test.
CCM has the same zero-key exposure as GCM: CBC-MAC and CTR run against the all-zero key schedule left by wc_AesInit. Add the keyInstalled guard to the software CCM encrypt/decrypt paths, mirroring GCM, and to the RISC-V port so it returns BAD_FUNC_ARG instead of KEYUSAGE_E. Extend aes_no_key_set_test with CCM cases.
In the software CFB/CFB1/CFB8/OFB helpers the sz == 0 early return came before the keyInstalled check, so a no-key call succeeded with sz == 0 but failed with sz > 0. Check the key first; crypto callbacks already ran in the callers. CBC keeps its sz == 0 return first because the DCP dispatch below it cannot handle sz == 0 (reads out + sz - 16); comment the ordering there.
The keyInstalled guard added to the shared aes.c mode functions is only set by aes.c's own key schedule and the RISC-V port. Ports that ship their own compile-time wc_AesSetKey/wc_AesGcmSetKey left keyInstalled at 0, so any AES mode that falls through to a guarded aes.c path returned BAD_FUNC_ARG after a correct key setup. Set keyInstalled at the key-install point of the affected ports so the shared mode guards accept a validly-keyed context: - silabs, af_alg, devcrypto, ti, caam: wc_AesSetKey - af_alg, kcapi: wc_AesGcmSetKey
The RISC-V assembly CBC encrypt/decrypt paths return before the generic keyInstalled guard, so wc_AesCbcEncrypt/Decrypt accepted a call with no key set. aes_no_key_set_test caught this on the riscv64-o0 config. Add the guard to the RISC-V dispatch, matching the other backends.
…6151) The keyInstalled guard added to the shared aes.c mode functions is set by every in-aes.c key-schedule branch except FREESCALE_LTC and FREESCALE_MMCAU. Their wc_AesSetKeyLocal installs the key but left keyInstalled at 0, so any guarded AES mode that falls through to a shared aes.c path returned an error after a valid key setup on those ports. Set keyInstalled at the key-install point in both branches, matching the other ports fixed earlier in this PR.
23363d7 to
37b9d4d
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10762
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
The AF_ALG AES mode overrides route through wc_AesSetup, which called setsockopt(ALG_SET_KEY, key, 0) with keylen 0 and returned WC_AFALG_SOCK_E instead of BAD_FUNC_ARG when no key was installed, so aes_no_key_set_test failed on WOLFSSL_AFALG. Reject setup when keyInstalled is 0, matching the software backend's no-key guard. Covers CBC, CTR, ECB and GCM.
Two independent hardening fixes with regression tests.
AES: reject mode operations when no key has been set (F-6151)
wc_AesInitzeroes theAesstruct, leavingkeylen/roundsat 0. The CTR/CBC/GCM/ECB/CFB/OFB entry points never verified a key had been installed viawc_AesSetKey, so calling them right afterwc_AesInitran the software cipher against an all-zero key schedule and returned success. For CTR this exposes a reconstructable keystream; for GCM the hash subkey H is also zero, making the tag forgeable.Adds the
keylen == 0guard (already used bywc_AesXtsEncrypt) to the software mode paths, placed after the crypto-callback fall-through so device-managed keys are unaffected. Newaes_no_key_set_test()(run fromaes_test()) confirms every mode rejects use before a key is set.RSA-PSS: test signature-to-message hash binding (F-6157)
wc_RsaPSS_CheckPadding_ex2binds a PSS signature to the message with a single comparison of the recomputed hash against the encoded hash H. Every positive PSS test passed the correct digest and the negative tests failed earlier on a wrong salt length, so removing the comparison left all default tests passing even though any well-formed PSS structure would verify against any message.After each successful sign/verify round-trip in
rsa_pss_test, flips one bit of the message digest, keeps the correct salt length, and requiresBAD_PADDING_E— making the hash comparison the deciding check.