Skip to content

Add SLH-DSA support to PKCS#7/CMS SignedData (RFC 9814) - #11720

Open
ducanhmit wants to merge 4 commits into
wolfSSL:masterfrom
ducanhmit:pkcs7-slhdsa-rfc9814
Open

ducanhmit wants to merge 4 commits into
wolfSSL:masterfrom
ducanhmit:pkcs7-slhdsa-rfc9814

Conversation

@ducanhmit

@ducanhmit ducanhmit commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

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.

  • signatureAlgorithm is the id-slh-dsa-* OID with absent parameters. On verify, parameters are rejected.
  • 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.

Testing

  • 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, a mismatched signatureAlgorithm, and signatureAlgorithm with NULL parameters.
  • Verify SignedData produced by OpenSSL, with and without signed attributes, plus a SHA-224 negative vector.
  • testwolfcrypt passes 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

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

Copilot AI balanced review requested due to automatic review settings October 12, 2026 16:13
@wolfSSL-Bot

Copy link
Copy Markdown

Can one of the admins verify this patch?

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

Partial key initialization leaks resources, and verification accepts forbidden signature parameters.

6 open findings
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.

Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/test/test.c Resolved
Comment thread certs/slhdsa/gen-slhdsa-mldsa-certs.sh Resolved
@kareem-wolfssl

Copy link
Copy Markdown
Contributor

OK to test- contributor agreement approved

@philljj

philljj commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Hi @ducanhmit, can you rebase this PR and mark it ready for review when it's ready.

Best,
Jordan

@philljj philljj assigned ducanhmit and unassigned wolfSSL-Bot Nov 5, 2026
@ducanhmit
ducanhmit force-pushed the pkcs7-slhdsa-rfc9814 branch from 2f20bbf to 1a546f8 Compare November 5, 2026 23:54
@ducanhmit
ducanhmit marked this pull request as ready for review November 5, 2026 23:55
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

retest this please

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

zephyr-frdm_rw612

  • FLASH: datas +16 B, log_const_area -8 B, rodata -8 B, text +3,688 B (+1.3%, 286,152 B / 67,108,864 B, total: 0% used)
  • RAM: bss +49 B, datas +16 B (+0.1%, 58,423 B / 983,040 B, total: 6% used)

No memory changes detected for:


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

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great work already, thank you so much for this! Some comments inline.

Comment thread wolfcrypt/test/test.c Resolved
Comment thread wolfcrypt/test/test.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/test/test.c Resolved
Comment thread wolfcrypt/test/test.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
Comment thread wolfcrypt/src/pkcs7.c Resolved
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.
@Frauschi

Copy link
Copy Markdown
Member

@claude review

Comment thread wolfcrypt/src/pkcs7.c
Comment on lines +1376 to +1384
ret = wc_PKCS7_SlhDsaParamFromOID((word32)keyOID, &param);
if (ret == 0) {
ret = wc_SlhDsaKey_Init(slhdsa, param, pkcs7->heap,
pkcs7->devId);
}
if (ret == 0) {
ret = wc_SlhDsaKey_PublicKeyDecode(key, &scratch, slhdsa,
keySz);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 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 wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread wolfcrypt/src/pkcs7.c
signedAttribSz, &msg, &msgSz,
&msgAlloc);
if (ret == 0) {
ret = wc_PKCS7_SlhDsaVerify(pkcs7, sig, (int)sigSz, msg, msgSz);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread wolfcrypt/src/pkcs7.c
* 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, &param);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread wolfcrypt/src/pkcs7.c Outdated
InitDecodedCert(dCert, pkcs7->cert[i], pkcs7->certSz[i], pkcs7->heap);

#ifdef WC_ASN_UNKNOWN_EXT_CB
if (pkcs7->unknownExtCallback != NULL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@Frauschi

Copy link
Copy Markdown
Member

@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.
@ducanhmit

Copy link
Copy Markdown
Contributor Author

Addressed the bot findings in 389f199, with tests.

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.

7 participants