Repository navigation
Conversation
|
Can one of the admins verify this patch? |
There was a problem hiding this comment.
🟡 Changes recommended
Partial key initialization leaks resources, and verification accepts forbidden signature parameters.
6 open findings
Free partially initialized SLH-DSA key on decode failure · New Clean up partial SLH-DSA initialization on signing error · New Free hash objects before returning on initialization error · New Reject SLH-DSA signatureAlgorithm parameters · New Release partially initialized SLH-DSA key in test cleanup · New Correct stale SLH-DSA test name in comment · New
What changed in this PR
Adds RFC 9814 SLH-DSA support to PKCS#7/CMS SignedData encoding and verification.
Changes:
- Supports all SLH-DSA parameter sets, digest defaults, and validation.
- Adds round-trip, negative, and OpenSSL interoperability tests.
- Distributes generated CMS test vectors.
| File | Description |
|---|---|
wolfcrypt/src/pkcs7.c |
Implements SLH-DSA signing and verification. |
wolfcrypt/test/test.c |
Adds SLH-DSA SignedData tests. |
certs/slhdsa/include.am |
Distributes interoperability vectors. |
certs/slhdsa/gen-slhdsa-mldsa-certs.sh |
Generates OpenSSL CMS vectors. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
OK to test- contributor agreement approved |
|
Hi @ducanhmit, can you rebase this PR and mark it ready for review when it's ready. Best, |
2f20bbf to
1a546f8
Compare
|
retest this please |
|
Frauschi
left a comment
There was a problem hiding this comment.
Great work already, thank you so much for this! Some comments inline.
Accept SLH-DSA signer certificates and keys for all twelve SHA2 and SHAKE parameter sets in wc_PKCS7_EncodeSignedData() and wc_PKCS7_VerifySignedData(), following the existing ML-DSA support. - signatureAlgorithm is the id-slh-dsa-* OID with absent parameters. - Pure SLH-DSA with an empty context signs the DER of the signed attributes, or the content itself when there are none. - When hashOID is not set, digestAlgorithm defaults to the hash RFC 9814 lists for the parameter set, encoded without parameters. - With signed attributes, a digest shorter than twice the SLH-DSA tree hash (2n) is rejected on encode and verify. - On verify, the signer certificate's key must match the parameter set named by signatureAlgorithm. - Pre-computed content hashes are rejected, as for ML-DSA. Tests sign and verify with every fast parameter set using generated certificates (the small sets use the existing 128s roots), check the digest defaults and limits and a mismatched signatureAlgorithm, and verify SignedData produced by OpenSSL.
Zero each SLH-DSA key after allocation and always call wc_SlhDsaKey_Free(), so a failed wc_SlhDsaKey_Init() no longer leaks a hash object. Reject signatureAlgorithm parameters for SLH-DSA, which RFC 9814 requires to be absent, and test it. Fix the interop vector comment in gen-slhdsa-mldsa-certs.sh.
Apply the SLH-DSA hashOID default and the 2n digest check in PKCS7_EncodeSigned() after the argument checks and before any output is written, instead of in the final signing pass. Document in the doxygen that hashOID is filled in for SLH-DSA signers. On verify, propagate errors such as NOT_COMPILED_IN from the digest check; only a too short digestAlgorithm is reported as SIG_VERIFY_E. Force SHAKE128 on for PKCS#7 with SLH-DSA SHAKE-128 parameter sets, as RFC 9814 makes it a MUST-support digest, and drop the SHA-2 fallbacks for the SHAKE sets. Remove the XMEMSET before wc_SlhDsaKey_Init(), which zeroes the key itself, and shorten the comments. Extend the ML-DSA comments, including the detached-over-empty-content limitation, to SLH-DSA. Tests: check for the expected error codes, skip the 128s test if no 128s parameter set is built, and drop the slow 128s no-signed-attributes vector, which pkcs7signed_slhdsa_params_test() covers on every fast set.
1a546f8 to
9a32597
Compare
|
@claude review |
| ret = wc_PKCS7_SlhDsaParamFromOID((word32)keyOID, ¶m); | ||
| if (ret == 0) { | ||
| ret = wc_SlhDsaKey_Init(slhdsa, param, pkcs7->heap, | ||
| pkcs7->devId); | ||
| } | ||
| if (ret == 0) { | ||
| ret = wc_SlhDsaKey_PublicKeyDecode(key, &scratch, slhdsa, | ||
| keySz); | ||
| } |
There was a problem hiding this comment.
🔴 Builds that leave some SLH-DSA parameter sets out now reject a whole SignedData or certs-only .p7b if its first certificate uses one of those sets. On the base branch such bundles parsed. wc_PKCS7_CheckPublicKeyDer used to accept SLH-DSA keys through its default case. It now returns NOT_COMPILED_IN from wc_PKCS7_SlhDsaParamFromOID at pkcs7.c:1376, and wc_PKCS7_VerifySignedData passes that error back. Fix: in this pre-check, treat NOT_COMPILED_IN as "skip the key check" (ret = 0). Signing or verifying with that set still fails later in wc_PKCS7_GetSignSize and wc_PKCS7_SlhDsaVerify.
Why this was flagged
wc_PKCS7_VerifySignedData calls wc_PKCS7_InitWithCert(pkcs7, cert, certSz) on the first entry of the certificates field (pkcs7.c:8587), for every SignedData, including degenerate certs-only bundles. InitWithCert runs wc_PKCS7_CheckPublicKeyDer on dCert->keyOID (pkcs7.c:1506). The new SLH-DSA case calls wc_PKCS7_SlhDsaParamFromOID, which returns NOT_COMPILED_IN for a known OID whose parameter set is not built (asn.c:5255). That error reaches the if (ret != 0) break; at pkcs7.c:8611 and decoding stops. ParseCert(NO_VERIFY) still stores keys for all 12 SLH-DSA OIDs (asn.c:14614-14627), so on the base branch the default case returned 0 and parsing went on. Plain --enable-slhdsa builds only SHAKE sets (configure.ac:2722-2729); SHA2 sets need the sha2 option. So in a default SLH-DSA build, any bundle whose first cert is SLH-DSA-SHA2 now fails.
Verification: asn.c:14613-14628 stores the key for all 12 SLH_DSA_*k OIDs. wc_PKCS7_SlhDsaParamFromOID (pkcs7.c:1376) passes on NOT_COMPILED_IN from asn.c:5255-5256. wc_PKCS7_VerifySignedData stops at if (ret != 0) break; (pkcs7.c:8611-8612). In the base wc_PKCS7_CheckPublicKeyDer, an SLH-DSA key fell through with ret=0.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #11720
Scan targets checked: wolfcrypt-src, wolfcrypt-bugs
Not reviewed (no scan target covers them): certs/slhdsa/cms-slhdsa-sha2-128s-noattr.der, certs/slhdsa/cms-slhdsa-shake-128s-sha224.der, certs/slhdsa/cms-slhdsa-shake-128s.der, certs/slhdsa/gen-slhdsa-mldsa-certs.sh, certs/slhdsa/include.am, configure.ac, doc/dox_comments/header_files/pkcs7.h
Coverage: 2 of 2 in-scope changed file(s) opened by the reviewer
Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
Review tier: Max
| signedAttribSz, &msg, &msgSz, | ||
| &msgAlloc); | ||
| if (ret == 0) { | ||
| ret = wc_PKCS7_SlhDsaVerify(pkcs7, sig, (int)sigSz, msg, msgSz); |
There was a problem hiding this comment.
SLH-DSA verify ignores the caller's pre-computed hash when there are no signed attributes · PKCS7/CMS verification semantics
Without signedAttrs, the new SLH-DSA case verifies over the bundle's own eContent and only length-checks the caller's hashBuf. An attacker can move a genuine signature's signed-attribute DER into eContent, and wc_PKCS7_VerifySignedData_ex() then returns 0 for any hash.
Suggested fix: When signedAttribSz == 0 in the SLH-DSA case, reject a caller-supplied hashBuf, or require it to equal the hash of the eContent being verified.
Basis: RFC 9814 Signed-Data Conventions: with signedAttrs absent, the pure SLH-DSA signature is computed over the content itself, so it does not bind a separately supplied content hash.
Related known findings (similar but distinct; listed for context, not part of this finding)
- F-16254: that report covers the ML-DSA case of the same switch; this is the newly added SLH-DSA case, which a fix to the ML-DSA case does not cover.
| * wc_SlhDsaKey_Init() ran, and releases any hash object that a | ||
| * failed Init set up. */ | ||
| XMEMSET(slhdsa, 0, sizeof(*slhdsa)); | ||
| ret = wc_PKCS7_SlhDsaParamFromOID((word32)keyOID, ¶m); |
There was a problem hiding this comment.
SLH-DSA certs of parameter sets that are not built now make InitWithCert and SignedData parsing fail · Logic errors
The new SLH-DSA case returns NOT_COMPILED_IN for SLH-DSA certs whose parameter set is not built (every SHA2 set in a default --enable-slhdsa build). ParseCert accepts these certs, but wc_PKCS7_InitWithCert(), and therefore wc_PKCS7_VerifySignedData(), now fails on any bundle whose first cert is one of them, including certs-only bundles.
Suggested fix: In the SLH-DSA case, treat NOT_COMPILED_IN from wc_PKCS7_SlhDsaParamFromOID() as success so certs with non-built parameter sets still load, as they did before this PR.
Basis: RFC 5652 Sections 5 and 5.1: SignedData may have zero signers in order to carry certificates, and its certificates field may contain more certificates than the signers need.
| InitDecodedCert(dCert, pkcs7->cert[i], pkcs7->certSz[i], pkcs7->heap); | ||
|
|
||
| #ifdef WC_ASN_UNKNOWN_EXT_CB | ||
| if (pkcs7->unknownExtCallback != NULL) |
There was a problem hiding this comment.
wc_PKCS7_SlhDsaVerify drops the 32-bit unknown-extension callback · Copy-paste errors
Unlike the ECDSA and ML-DSA verifiers and wc_PKCS7_InitWithCert(), this function sets only unknownExtCallback, not unknownExtCallback32. A signer cert whose critical extension is accepted through the 32-bit callback therefore fails ParseCert here, and verification returns SIG_VERIFY_E.
Suggested fix: The fix needs a design decision on callback parity; consult the wc_PKCS7_SetUnknownExtCallback32() API documentation and match how the ECDSA and ML-DSA verifiers handle it.
|
@ducanhmit please review the new review comments from the bots. |
Keep loading SLH-DSA certificates whose parameter set is not built in wc_PKCS7_CheckPublicKeyDer(), as before SLH-DSA support was added, so bundles with such a certificate first (e.g. certs-only) still parse. Reject a caller-supplied content hash when verifying SLH-DSA SignedData without signed attributes, as the signature is over the eContent and doesn't bind it. Also set the 32-bit unknown extension callback in wc_PKCS7_SlhDsaVerify(), like the other verifiers.
|
Addressed the bot findings in 389f199, with tests. |


Description
Accept SLH-DSA signer certificates and keys for all twelve SHA2 and SHAKE parameter sets in wc_PKCS7_EncodeSignedData() and wc_PKCS7_VerifySignedData(), following the existing ML-DSA support.
Testing
testwolfcryptpasses with--enable-slhdsa --enable-mldsa --enable-pkcs7, with and without--enable-certgen, with SLH-DSA verify-only, with SLH-DSA or ML-DSA disabled, and under ASan/UBSan.Checklist