From 1e090761a17099a1c5562d2166ae2d2f1ee2d4e2 Mon Sep 17 00:00:00 2001 From: Duc Anh Luu Date: Mon, 12 Oct 2026 22:52:56 +0700 Subject: [PATCH 1/4] Add SLH-DSA support to PKCS#7/CMS SignedData (RFC 9814) 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. --- certs/slhdsa/cms-slhdsa-sha2-128s-noattr.der | Bin 0 -> 16627 bytes certs/slhdsa/cms-slhdsa-shake-128s-sha224.der | Bin 0 -> 16802 bytes certs/slhdsa/cms-slhdsa-shake-128s.der | Bin 0 -> 16806 bytes certs/slhdsa/gen-slhdsa-mldsa-certs.sh | 22 + certs/slhdsa/include.am | 3 + wolfcrypt/src/pkcs7.c | 510 +++++++++++++- wolfcrypt/test/test.c | 657 ++++++++++++++++++ 7 files changed, 1187 insertions(+), 5 deletions(-) create mode 100644 certs/slhdsa/cms-slhdsa-sha2-128s-noattr.der create mode 100644 certs/slhdsa/cms-slhdsa-shake-128s-sha224.der create mode 100644 certs/slhdsa/cms-slhdsa-shake-128s.der diff --git a/certs/slhdsa/cms-slhdsa-sha2-128s-noattr.der b/certs/slhdsa/cms-slhdsa-sha2-128s-noattr.der new file mode 100644 index 0000000000000000000000000000000000000000..54326c461272dfb41fe9682e91ef2ecddcfd4172 GIT binary patch literal 16627 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 literal 0 HcmV?d00001 diff --git a/certs/slhdsa/cms-slhdsa-shake-128s.der b/certs/slhdsa/cms-slhdsa-shake-128s.der new file mode 100644 index 0000000000000000000000000000000000000000..95c62d87b889fa1c64f48e9a55c3704b087335b1 GIT binary patch literal 16806 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%0DIe6O~baF zBqhra50#62x_+kmlb%zU@y|z3bgw&jfJ$fyRvz)0e(h;gQh1BU-x2d`T&5R*TIUY{ z_2#q(-SxX8yNT$qSOgxA5<-&V*PWP>r^=C?OM!d4KbhXE(_R54_tsdsagcdf0%2=J zcKAuS=C`23HA*KP6n%7vEBeh=>CeVMiCm zdbIPIIf9m?!!uQ#u0Q5bzo|Z5}rHmCskC>V?6zz z2RGw}j2afvXXM1y+%h31z5j~x)261FlpLZOiwFoKmDmPH-JsQR0jrK~Uw|@KBsE~` zBL&rljqrI%ml^NJu-=^Y`9B#&!Fh&^TEU@0k(%IkTDc|hwV+D>I$;ugvaFpTIR)Sw z`g^e>^GEeSZ-^P>&-hOuEk6Qvkj=N~a_k#g&~AXAfu9GGj&+b2>qVk;xC}I9{nl%T zz|yl~@~S4-`4g6Uo$yHy;Q8~i(Gd?q7+amcv_p9v`!m3>lMv~ws_D0kF1bd9URd&<=d$^sZeFg?yx~wi2uF@% zQLeNxlS;WQV)x5ra8C_JEhXmg^;$Qcsah9@A0~u8d_v5EidSpOt!UJ@w`-QSwpd<{ zuZ$AV1`Fy;sDLa$ztPONkdiCik4G4WRT$~znA=k-;+|91DZio849e6)E%}9VmP}#E z5azrZkv99`e-TSSB!FaaeiJRm1VS%_{Vb&<!2r|2?%ty+Mg&t@_POTezgA}L){v}3#AL0 zf(7sf1Uu3n-IzN1v#0bCZhWoCk~j*CP8(y4UBRUrY^D90JDFBZ3VGe=rA;7)83HJW zL~EWiSFxKZo|k3g&_rYdD;{7X-L%tM!^j9u`Igm;fAQGliQP9>r7$vrlHZK3wICHW zZ;e<#f6yVd9EM%?#i<{4l!1E6Iq*x+6|>{6U8_jR)F#!?iu#p-J##X*nc?}SZtPhl zxHwSh#kz&rKT&PDTY|zsVB^%4S9kzFs4U20rtftRT(2t+>uJSIAyfyxZO6W&`LuR8 zV=ZQJfC`hNbe7S}?lHH}|Ehvo8haR!Qe`w0Jn|Og&wS&f@C<7LDraWVVUW&046iP( zmRj8%Ldm-nUfDdES`-=?8Sfc^JN@+@F4NFBlfp*R zx6Iz{@2`+em~qE6dQmjsNV6Ns>U(e`G@TW!piS%D&qG$9Ptj_&QgS)%S^g?$yk3VH zyp6KjMcDoe41p&O5-n=Y0=-~gh95*!nP&6@QaFsB2iNO49Y-#*9fCbia$({H(1d!H zNuaF3=_`Yo@{9~o&C?%AhoQ&9ujHPA;?tOwgQqF`S=q3R5dxh zpP(O~k1#OM9f`uV&_P%{DczuQZujeyz;W_YTa+O4(aWL;s|uU7$E4MKN8XZbKzbRj zYzVxV>?+jotbcY8vK5g?{<3iyM~RAvAgV=X4}C1@DIqk5QSGr4Pv+Nc5^_Qg$i8#) zp>??7Pf4pYZpUOKOk^q&e|tR^nhIG~Wr7_&<;Skq{NH69Ghz~?TlPP7_%1386&RkiAW~GnG3H(={7>nwJAh0{ z`Zdq{?;J^H#|wI}=ViGJ))m}Ngqa9#e7WQpB=a7N@hkCRe^~4!ZfPNz!*pjK_Qwgi z4_8w>2sO!|FWjd{ydg(s$zy^J2<3gXQCFL95b;dl$(2~ zj^ "${BENCH}_pub.bin" rm -f "${BENCH}_pub.der" +# RFC 9814 SignedData interop vectors, verified by pkcs7signed_slhdsa_test() +# in wolfcrypt/test/test.c. One per hash family, covering both the +# signed-attributes and the no-signed-attributes form. OpenSSL picks the +# RFC 9814 digestAlgorithm for the parameter set. +printf 'RFC 9814 SLH-DSA SignedData from OpenSSL' > cms-content.tmp +openssl cms -sign -binary -nodetach -outform DER -in cms-content.tmp \ + -signer root-slhdsa-shake-128s.pem -inkey root-slhdsa-shake-128s-priv.pem \ + -out cms-slhdsa-shake-128s.der +check_result $? "Sign SLH-DSA-SHAKE-128s SignedData" +openssl cms -sign -binary -nodetach -noattr -outform DER -in cms-content.tmp \ + -signer root-slhdsa-sha2-128s.pem -inkey root-slhdsa-sha2-128s-priv.pem \ + -out cms-slhdsa-sha2-128s-noattr.der +check_result $? "Sign SLH-DSA-SHA2-128s SignedData without attributes" +# Negative vector: SHA-224 is shorter than RFC 9814 allows for 128-bit +# parameter sets when signed attributes are present. Must fail to verify. +openssl cms -sign -binary -nodetach -md sha224 -outform DER \ + -in cms-content.tmp -signer root-slhdsa-shake-128s.pem \ + -inkey root-slhdsa-shake-128s-priv.pem \ + -out cms-slhdsa-shake-128s-sha224.der +check_result $? "Sign SLH-DSA-SHAKE-128s SignedData with SHA-224" +rm -f cms-content.tmp + echo echo "All SLH-DSA / ML-DSA-44 test certificates regenerated." diff --git a/certs/slhdsa/include.am b/certs/slhdsa/include.am index c2a9ebcb274..aadab34f42e 100644 --- a/certs/slhdsa/include.am +++ b/certs/slhdsa/include.am @@ -28,6 +28,9 @@ EXTRA_DIST += \ certs/slhdsa/root-slhdsa-sha2-128s-priv.der \ certs/slhdsa/root-slhdsa-sha2-128s.pem \ certs/slhdsa/root-slhdsa-sha2-128s.der \ + certs/slhdsa/cms-slhdsa-shake-128s.der \ + certs/slhdsa/cms-slhdsa-shake-128s-sha224.der \ + certs/slhdsa/cms-slhdsa-sha2-128s-noattr.der \ certs/slhdsa/server-mldsa44-sha2.pem \ certs/slhdsa/client-mldsa44-sha2.pem \ certs/slhdsa/server-slhdsa-shake-128f-priv.pem \ diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 10eba731316..7bcf1539903 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -88,6 +88,14 @@ #define WC_PKCS7_MLDSA_VERIFY #endif #endif +#ifdef WOLFSSL_HAVE_SLHDSA + #include + #define WC_PKCS7_HAVE_SLHDSA + #ifndef WOLFSSL_SLHDSA_VERIFY_ONLY + #define WC_PKCS7_SLHDSA_SIGN + #endif + #define WC_PKCS7_SLHDSA_VERIFY +#endif #ifdef HAVE_LIBZ #include #endif @@ -1183,6 +1191,11 @@ static int wc_PKCS7_RecipientListVersionsAllZero(wc_PKCS7* pkcs7) static int wc_PKCS7_MlDsaLevelFromOID(word32 publicKeyOID, byte* level); #endif +#ifdef WC_PKCS7_HAVE_SLHDSA +static int wc_PKCS7_SlhDsaParamFromOID(word32 publicKeyOID, + enum SlhDsaParam* param); +#endif + /* Returns whether the parameters field of a CMS structural digest * AlgorithmIdentifier (SignedData.digestAlgorithms / SignerInfo.digestAlgorithm) * should be omitted (absent) rather than encoded as NULL. @@ -1203,6 +1216,13 @@ static byte wc_PKCS7_DigestParamsAbsent(const wc_PKCS7* pkcs7) if (pkcs7->hashOID == SHAKE256h || pkcs7->hashOID == SHAKE128h) { return 1; } +#endif +#ifdef WC_PKCS7_HAVE_SLHDSA + /* RFC 9814 Section 4: digestAlgorithm SHOULD NOT include parameters. + * There is no legacy SLH-DSA output to stay compatible with. */ + if (wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { + return 1; + } #endif return pkcs7->hashParamsAbsent; } @@ -1327,6 +1347,41 @@ static int wc_PKCS7_CheckPublicKeyDer(wc_PKCS7* pkcs7, int keyOID, XFREE(mldsa, pkcs7->heap, DYNAMIC_TYPE_MLDSA); break; } +#endif +#ifdef WC_PKCS7_HAVE_SLHDSA + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + enum SlhDsaParam param; + SlhDsaKey* slhdsa = (SlhDsaKey*)XMALLOC(sizeof(SlhDsaKey), + pkcs7->heap, DYNAMIC_TYPE_SLHDSA); + if (slhdsa == NULL) { + ret = MEMORY_E; + break; + } + 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); + wc_SlhDsaKey_Free(slhdsa); + } + XFREE(slhdsa, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); + break; + } #endif } @@ -2556,6 +2611,28 @@ static int wc_PKCS7_GetSignSize(wc_PKCS7* pkcs7) } break; #endif + + #ifdef WC_PKCS7_SLHDSA_SIGN + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + enum SlhDsaParam param; + ret = wc_PKCS7_SlhDsaParamFromOID(pkcs7->publicKeyOID, ¶m); + if (ret == 0) + ret = wc_SlhDsaKey_SigSizeFromParam(param); + } + break; + #endif } return ret; @@ -2876,6 +2953,12 @@ static int wc_PKCS7_SignedDataGetEncAlgoId(wc_PKCS7* pkcs7, int* digEncAlgoId, algoId = (int)pkcs7->publicKeyOID; } #endif +#ifdef WC_PKCS7_HAVE_SLHDSA + else if (wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { + algoType = oidSigType; + algoId = (int)pkcs7->publicKeyOID; + } +#endif if (algoId == 0) { WOLFSSL_MSG("Invalid signature algorithm type"); @@ -3021,9 +3104,10 @@ static int wc_PKCS7_MlDsaLevelFromOID(word32 publicKeyOID, byte* level) } } #endif +#endif /* WC_PKCS7_HAVE_MLDSA */ /* Build the exact octet string that a "pure" PQC signature is computed over, - * per RFC 9882 Section 4: + * per RFC 9882 Section 4 (ML-DSA) and RFC 9814 Section 4 (SLH-DSA): * - if signed attributes are present, the DER encoding of the SignedAttrs * SET OF (i.e. the [0] IMPLICIT attributes re-tagged to a universal SET); * - otherwise, the eContent of the SignedData directly. @@ -3036,7 +3120,8 @@ static int wc_PKCS7_MlDsaLevelFromOID(word32 publicKeyOID, byte* level) * attribs/attribsSz are the flattened attributes without the SET wrapper, as * available on both the encode (flatSignedAttribs) and decode (signedAttrib) * paths. */ -#if defined(WC_PKCS7_MLDSA_SIGN) || defined(WC_PKCS7_MLDSA_VERIFY) +#if defined(WC_PKCS7_MLDSA_SIGN) || defined(WC_PKCS7_MLDSA_VERIFY) || \ + defined(WC_PKCS7_SLHDSA_SIGN) || defined(WC_PKCS7_SLHDSA_VERIFY) static int wc_PKCS7_BuildPureSigMessage(wc_PKCS7* pkcs7, const byte* attribs, word32 attribsSz, byte** outMsg, word32* outMsgSz, int* outAlloc) { @@ -3090,8 +3175,10 @@ static int wc_PKCS7_BuildPureSigMessage(wc_PKCS7* pkcs7, const byte* attribs, return 0; } -#endif /* WC_PKCS7_MLDSA_SIGN || WC_PKCS7_MLDSA_VERIFY */ +#endif /* WC_PKCS7_MLDSA_SIGN || WC_PKCS7_MLDSA_VERIFY || + * WC_PKCS7_SLHDSA_SIGN || WC_PKCS7_SLHDSA_VERIFY */ +#ifdef WC_PKCS7_HAVE_MLDSA #if !defined(WOLFSSL_MLDSA_NO_SIGN) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) /* Sign the supplied message with the ML-DSA private key in pkcs7->privateKey, * writing the signature into the shared esd->encContentDigest buffer (which the @@ -3155,6 +3242,177 @@ static int wc_PKCS7_MlDsaSign(wc_PKCS7* pkcs7, const byte* msg, word32 msgSz, #endif /* WC_PKCS7_HAVE_MLDSA */ +#ifdef WC_PKCS7_HAVE_SLHDSA +/* + * SLH-DSA (FIPS 205) SignedData support, per RFC 9814. + * + * Follows the same "pure" signing model as ML-DSA (RFC 9882): the signature + * is computed over the complete message with an empty context string and + * absent signatureAlgorithm parameters. The shared helper + * wc_PKCS7_BuildPureSigMessage() constructs the message to sign/verify. + */ + +/* Map an SLH-DSA public key OID to its parameter set. + * Returns 0 and sets *param on success, NOT_COMPILED_IN if the parameter set + * is not built, BAD_FUNC_ARG otherwise. */ +static int wc_PKCS7_SlhDsaParamFromOID(word32 publicKeyOID, + enum SlhDsaParam* param) +{ + int p = wc_SlhDsaOidToParam((int)publicKeyOID); + + if (p == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + return NOT_COMPILED_IN; + } + if (p < 0) { + return BAD_FUNC_ARG; + } + *param = (enum SlhDsaParam)p; + return 0; +} + +/* RFC 9814 Section 4: digestAlgorithm to use for an SLH-DSA signer when the + * caller did not set pkcs7->hashOID. These are the hash functions that + * receivers MUST support for each parameter set. Falls back to the SHA-2 + * function of the same size when the SHAKE variant is not compiled in. + * + * Returns hash OID, or 0 if none is available. */ +static int wc_PKCS7_SlhDsaDefaultHashOID(word32 publicKeyOID) +{ + enum SlhDsaParam param; + + if (wc_PKCS7_SlhDsaParamFromOID(publicKeyOID, ¶m) != 0) { + return 0; + } + + switch (param) { + case SLHDSA_SHAKE128S: + case SLHDSA_SHAKE128F: + #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE128) + return SHAKE128h; + #elif !defined(NO_SHA256) + return SHA256h; + #else + return 0; + #endif + case SLHDSA_SHAKE192S: + case SLHDSA_SHAKE192F: + case SLHDSA_SHAKE256S: + case SLHDSA_SHAKE256F: + #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) + return SHAKE256h; + #elif defined(WOLFSSL_SHA512) + return SHA512h; + #else + return 0; + #endif + #ifdef WOLFSSL_SLHDSA_SHA2 + case SLHDSA_SHA2_128S: + case SLHDSA_SHA2_128F: + #ifndef NO_SHA256 + return SHA256h; + #else + return 0; + #endif + case SLHDSA_SHA2_192S: + case SLHDSA_SHA2_192F: + case SLHDSA_SHA2_256S: + case SLHDSA_SHA2_256F: + #ifdef WOLFSSL_SHA512 + return SHA512h; + #else + return 0; + #endif + #endif + default: + return 0; + } +} + +/* RFC 9814 Section 4: when signed attributes are present, the digestAlgorithm + * MUST produce a hash value at least twice the size of the hash used in the + * SLH-DSA tree (n). An SLH-DSA public key is 2n bytes long, so compare the + * digest size against the public key size. + * + * Returns 0 if hashOID is strong enough, BAD_FUNC_ARG otherwise. */ +static int wc_PKCS7_SlhDsaCheckDigest(word32 publicKeyOID, int hashOID) +{ + enum SlhDsaParam param; + int ret; + int minSz; + int digSz; + + ret = wc_PKCS7_SlhDsaParamFromOID(publicKeyOID, ¶m); + if (ret != 0) { + return ret; + } + + minSz = wc_SlhDsaKey_PublicSizeFromParam(param); + digSz = wc_HashGetDigestSize(wc_OidGetHash(hashOID)); + if (minSz <= 0 || digSz < minSz) { + WOLFSSL_MSG("SLH-DSA digestAlgorithm too weak for parameter set"); + return BAD_FUNC_ARG; + } + + return 0; +} + +#ifdef WC_PKCS7_SLHDSA_SIGN +/* Sign the supplied message with the SLH-DSA private key in pkcs7->privateKey, + * writing the signature into esd->encContentDigest. Uses pure SLH-DSA with an + * empty context string, per RFC 9814. + * + * Returns the signature length on success, negative on error. */ +static int wc_PKCS7_SlhDsaSign(wc_PKCS7* pkcs7, const byte* msg, word32 msgSz, + ESD* esd) +{ + int ret; + enum SlhDsaParam param; + word32 idx = 0; + word32 sigSz; + SlhDsaKey* key; + + if (pkcs7 == NULL || esd == NULL || msg == NULL || + esd->encContentDigest == NULL || + pkcs7->privateKey == NULL || pkcs7->privateKeySz == 0) { + return BAD_FUNC_ARG; + } + + ret = wc_PKCS7_SlhDsaParamFromOID(pkcs7->publicKeyOID, ¶m); + if (ret != 0) { + return ret; + } + + key = (SlhDsaKey*)XMALLOC(sizeof(SlhDsaKey), pkcs7->heap, + DYNAMIC_TYPE_SLHDSA); + if (key == NULL) { + return MEMORY_E; + } + + ret = wc_SlhDsaKey_Init(key, param, pkcs7->heap, pkcs7->devId); + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); + ret = wc_SlhDsaKey_PrivateKeyDecode(pkcs7->privateKey, &idx, key, + pkcs7->privateKeySz); + PRIVATE_KEY_LOCK(); + if (ret == 0) { + sigSz = esd->encContentDigestBufSz; + ret = wc_SlhDsaKey_Sign(key, NULL, 0, msg, msgSz, + esd->encContentDigest, &sigSz, + pkcs7->rng); + } + wc_SlhDsaKey_Free(key); + } + XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); + + if (ret == 0) { + return (int)sigSz; + } + return ret; +} +#endif /* WC_PKCS7_SLHDSA_SIGN */ +#endif /* WC_PKCS7_HAVE_SLHDSA */ + + /* build SignedData signature over DigestInfo or content digest * * pkcs7 - pointer to initialized PKCS7 struct @@ -3197,6 +3455,11 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, skipDigestInfo = 1; } #endif +#ifdef WC_PKCS7_HAVE_SLHDSA + if (wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { + skipDigestInfo = 1; + } +#endif if (!skipDigestInfo) { ret = wc_PKCS7_BuildDigestInfo(pkcs7, flatSignedAttribs, @@ -3311,6 +3574,44 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, } #endif +#ifdef WC_PKCS7_SLHDSA_SIGN + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + byte* msg = NULL; + word32 msgSz = 0; + int msgAlloc = 0; + + if (flatSignedAttribsSz > 0) { + ret = wc_PKCS7_SlhDsaCheckDigest(pkcs7->publicKeyOID, + pkcs7->hashOID); + if (ret != 0) { + break; + } + } + ret = wc_PKCS7_BuildPureSigMessage(pkcs7, flatSignedAttribs, + flatSignedAttribsSz, &msg, + &msgSz, &msgAlloc); + if (ret == 0) { + ret = wc_PKCS7_SlhDsaSign(pkcs7, msg, msgSz, esd); + if (msgAlloc) { + XFREE(msg, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + } + break; + } +#endif + default: WOLFSSL_MSG("Unsupported public key type"); ret = BAD_FUNC_ARG; @@ -3678,6 +3979,16 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, byte signingTime[MAX_TIME_STRING_SZ]; +#ifdef WC_PKCS7_HAVE_SLHDSA + /* RFC 9814 names a digestAlgorithm for each SLH-DSA parameter set, so use + * it when the caller has not chosen one */ + if (pkcs7 != NULL && pkcs7->hashOID == 0 && + pkcs7->sidType != DEGENERATE_SID && + wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { + pkcs7->hashOID = wc_PKCS7_SlhDsaDefaultHashOID(pkcs7->publicKeyOID); + } +#endif + /* hashOID is unused on the degenerate (certs-only) path, so only require * it when an actual signer is present */ if (pkcs7 == NULL || @@ -3730,6 +4041,12 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, return BAD_FUNC_ARG; } #endif +#ifdef WC_PKCS7_HAVE_SLHDSA + if (hashBuf != NULL && wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { + WOLFSSL_MSG("Pre-calculated content hash not supported for SLH-DSA"); + return BAD_FUNC_ARG; + } +#endif #if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) keyIdSize = wc_HashGetDigestSize(wc_HashTypeConvert(HashIdAlg( @@ -5126,7 +5443,8 @@ int wc_PKCS7_SetEccSignRawDigestCb(wc_PKCS7* pkcs7, CallbackEccSignRawDigest cb) #endif /* HAVE_ECC */ -#if !defined(NO_RSA) || defined(HAVE_ECC) || defined(WC_PKCS7_MLDSA_VERIFY) +#if !defined(NO_RSA) || defined(HAVE_ECC) || defined(WC_PKCS7_MLDSA_VERIFY) \ + || defined(WC_PKCS7_SLHDSA_VERIFY) /* Check whether the given decoded certificate matches the SignerIdentifier * (sid) field of the currently parsed SignerInfo. Per RFC 5652 Section 5.3, * the sid selects which certificate's public key must be used to verify the @@ -5212,7 +5530,8 @@ static int wc_PKCS7_CertMatchesSignerInfo(wc_PKCS7* pkcs7, DecodedCert* dCert) return 0; } -#endif /* !NO_RSA || HAVE_ECC || WC_PKCS7_MLDSA_VERIFY */ +#endif /* !NO_RSA || HAVE_ECC || WC_PKCS7_MLDSA_VERIFY || + * WC_PKCS7_SLHDSA_VERIFY */ #ifndef NO_RSA @@ -5826,6 +6145,124 @@ static int wc_PKCS7_MlDsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, * WOLFSSL_MLDSA_PUBLIC_KEY */ +#ifdef WC_PKCS7_SLHDSA_VERIFY +/* Verify a pure SLH-DSA SignedData signature (RFC 9814) over the supplied + * message, using an empty context string. Tries each certificate in the + * bundle, as wc_PKCS7_MlDsaVerify() does; the certificate key must be of the + * parameter set named by signatureAlgorithm. + * + * returns 0 on success, negative on error */ +static int wc_PKCS7_SlhDsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, + const byte* msg, word32 msgSz) +{ + int ret = 0, i; + int verified = 0; + enum SlhDsaParam param; + SlhDsaKey* key = NULL; + WC_DECLARE_VAR(dCert, DecodedCert, 1, 0); + word32 idx; + + if (pkcs7 == NULL || sig == NULL || msg == NULL) { + return BAD_FUNC_ARG; + } + + ret = wc_PKCS7_SlhDsaParamFromOID(pkcs7->publicKeyOID, ¶m); + if (ret != 0) { + return ret; + } + + key = (SlhDsaKey*)XMALLOC(sizeof(SlhDsaKey), pkcs7->heap, + DYNAMIC_TYPE_SLHDSA); + if (key == NULL) { + return MEMORY_E; + } + + WC_ALLOC_VAR_EX(dCert, DecodedCert, 1, pkcs7->heap, DYNAMIC_TYPE_DCERT, + { XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); + return MEMORY_E; }); + + for (i = 0; i < MAX_PKCS7_CERTS; i++) { + + verified = 0; + idx = 0; + + if (pkcs7->certSz[i] == 0) + continue; + + ret = wc_SlhDsaKey_Init(key, param, pkcs7->heap, pkcs7->devId); + if (ret != 0) { + XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); + WC_FREE_VAR_EX(dCert, pkcs7->heap, DYNAMIC_TYPE_DCERT); + return ret; + } + + InitDecodedCert(dCert, pkcs7->cert[i], pkcs7->certSz[i], pkcs7->heap); + +#ifdef WC_ASN_UNKNOWN_EXT_CB + if (pkcs7->unknownExtCallback != NULL) + wc_SetUnknownExtCallback(dCert, pkcs7->unknownExtCallback); +#endif + + ret = ParseCert(dCert, CA_TYPE, NO_VERIFY, 0); + if (ret < 0) { + WOLFSSL_MSG("ASN SLH-DSA cert parse error"); + FreeDecodedCert(dCert); + wc_SlhDsaKey_Free(key); + continue; + } + + if (pkcs7->signerInfo != NULL && pkcs7->signerInfo->sid != NULL && + !wc_PKCS7_CertMatchesSignerInfo(pkcs7, dCert)) { + FreeDecodedCert(dCert); + wc_SlhDsaKey_Free(key); + continue; + } + + if (dCert->keyOID != pkcs7->publicKeyOID) { + FreeDecodedCert(dCert); + wc_SlhDsaKey_Free(key); + continue; + } + + ret = wc_SlhDsaKey_PublicKeyDecode(dCert->publicKey, &idx, key, + dCert->pubKeySize); + if (ret < 0) { + WOLFSSL_MSG("ASN SLH-DSA key decode error"); + FreeDecodedCert(dCert); + wc_SlhDsaKey_Free(key); + continue; + } + + ret = wc_SlhDsaKey_Verify(key, NULL, 0, msg, msgSz, sig, + (word32)sigSz); + + if (ret == 0) { + verified = 1; + XMEMCPY(pkcs7->issuerSubjKeyId, dCert->extSubjKeyId, KEYID_SIZE); + pkcs7->verifyCert = pkcs7->cert[i]; + pkcs7->verifyCertSz = pkcs7->certSz[i]; + } + + wc_SlhDsaKey_Free(key); + FreeDecodedCert(dCert); + + if (ret == 0) { + break; + } + } + + if (verified == 0) { + ret = SIG_VERIFY_E; + } + + XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); + WC_FREE_VAR_EX(dCert, pkcs7->heap, DYNAMIC_TYPE_DCERT); + + return ret; +} +#endif /* WC_PKCS7_SLHDSA_VERIFY */ + + /* build SignedData digest, both in PKCS#7 DigestInfo format and * as plain digest for CMS. * @@ -6184,6 +6621,15 @@ static int wc_PKCS7_SignedDataVerifySignature(wc_PKCS7* pkcs7, byte* sig, return BAD_FUNC_ARG; } #endif +#ifdef WC_PKCS7_HAVE_SLHDSA + if (wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { + WOLFSSL_MSG("SLH-DSA external verify without a certificate in " + "the bundle is not supported"); + WC_FREE_VAR_EX(pkcs7Digest, pkcs7->heap, + DYNAMIC_TYPE_TMP_BUFFER); + return BAD_FUNC_ARG; + } +#endif /* store signature */ XFREE(pkcs7->signature, pkcs7->heap, DYNAMIC_TYPE_SIGNATURE); @@ -6314,6 +6760,43 @@ static int wc_PKCS7_SignedDataVerifySignature(wc_PKCS7* pkcs7, byte* sig, } #endif +#ifdef WC_PKCS7_SLHDSA_VERIFY + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + byte* msg = NULL; + word32 msgSz = 0; + int msgAlloc = 0; + + if (signedAttribSz > 0 && + wc_PKCS7_SlhDsaCheckDigest(pkcs7->publicKeyOID, + pkcs7->hashOID) != 0) { + ret = SIG_VERIFY_E; + break; + } + ret = wc_PKCS7_BuildPureSigMessage(pkcs7, signedAttrib, + signedAttribSz, &msg, &msgSz, + &msgAlloc); + if (ret == 0) { + ret = wc_PKCS7_SlhDsaVerify(pkcs7, sig, (int)sigSz, msg, msgSz); + if (msgAlloc) { + XFREE(msg, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + } + break; + } +#endif + default: WOLFSSL_MSG("Unsupported public key type"); ret = BAD_FUNC_ARG; @@ -6406,6 +6889,23 @@ static int wc_PKCS7_SetPublicKeyOID(wc_PKCS7* pkcs7, int sigOID) break; #endif + #ifdef WC_PKCS7_HAVE_SLHDSA + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + pkcs7->publicKeyOID = (word32)sigOID; + break; + #endif + default: WOLFSSL_MSG("Unsupported public key algorithm"); return ASN_SIG_KEY_E; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 5ce1415395b..0c30f396a98 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -91674,6 +91674,645 @@ static wc_test_ret_t pkcs7signed_mldsa_test(void) #endif /* WOLFSSL_HAVE_MLDSA && sign && verify && filesystem */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(NO_FILESYSTEM) && \ + !defined(NO_ASN) +/* Verify RFC 9814 SignedData produced by OpenSSL (see + * certs/slhdsa/gen-slhdsa-mldsa-certs.sh). The SHA-224 vector carries a valid + * signature, but SHA-224 is shorter than twice the SLH-DSA-128 tree hash, so + * RFC 9814 Section 4 forbids it with signed attributes. */ +static wc_test_ret_t pkcs7signed_slhdsa_interop_test(void) +{ + static const struct { + const char* file; + int hashOID; + int expRet; + } vectors[] = { +#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128S) && defined(WOLFSSL_SHA3) && \ + defined(WOLFSSL_SHAKE128) + { CERT_ROOT "slhdsa" CERT_PATH_SEP "cms-slhdsa-shake-128s.der", + SHAKE128h, 0 }, +#endif +#if !defined(WOLFSSL_SLHDSA_PARAM_NO_128S) && defined(WOLFSSL_SHA224) + { CERT_ROOT "slhdsa" CERT_PATH_SEP "cms-slhdsa-shake-128s-sha224.der", + SHA224h, WC_NO_ERR_TRACE(SIG_VERIFY_E) }, +#endif +#if defined(WOLFSSL_SLHDSA_SHA2) && \ + !defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128S) && !defined(NO_SHA256) + { CERT_ROOT "slhdsa" CERT_PATH_SEP "cms-slhdsa-sha2-128s-noattr.der", + SHA256h, 0 }, +#endif + { NULL, 0, 0 } + }; + static const byte content[] = "RFC 9814 SLH-DSA SignedData from OpenSSL"; + wc_test_ret_t ret = 0; + wc_PKCS7* pkcs7 = NULL; + XFILE f = XBADFILE; + byte* in = NULL; + word32 inSz; + int i; + + in = (byte*)XMALLOC(FOURK_BUF * 6, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (in == NULL) + return WC_TEST_RET_ENC_ERRNO; + + for (i = 0; vectors[i].file != NULL; i++) { + f = XFOPEN(vectors[i].file, "rb"); + if (f == XBADFILE) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + inSz = (word32)XFREAD(in, 1, FOURK_BUF * 6, f); + XFCLOSE(f); + f = XBADFILE; + if (inSz == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + ret = wc_PKCS7_InitWithCert(pkcs7, NULL, 0); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + ret = wc_PKCS7_VerifySignedData(pkcs7, in, inSz); + if (vectors[i].expRet != 0) { + if (ret != vectors[i].expRet) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + ret = 0; + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + continue; + } + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + if (pkcs7->hashOID != vectors[i].hashOID || + pkcs7->contentSz != (word32)sizeof(content) - 1 || + XMEMCMP(pkcs7->content, content, sizeof(content) - 1) != 0) { + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + +out_lbl: + if (f != XBADFILE) + XFCLOSE(f); + wc_PKCS7_Free(pkcs7); + XFREE(in, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA && !NO_FILESYSTEM && !NO_ASN */ + +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) && \ + !defined(NO_FILESYSTEM) && !defined(NO_ASN) + +typedef struct { + const char* certFile; + const char* keyFile; + int hashOID; + int noSignedAttribs; +} pkcs7SlhDsaVector; + +static wc_test_ret_t pkcs7signed_slhdsa_test(void) +{ + wc_test_ret_t ret = 0; + WC_RNG rng; + wc_PKCS7* pkcs7 = NULL; + XFILE f = XBADFILE; + byte* cert = NULL; + byte* key = NULL; + byte* out = NULL; + word32 certSz, keySz; + word32 outHeadSz, outFootSz; + int encodedSz; + int i, testSz; + int rngInit = 0; + byte preHash[WC_MAX_DIGEST_SIZE]; + + WOLFSSL_SMALL_STACK_STATIC const byte content[] = { + 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x50,0x51,0x43 + }; + + #define SLHDSA_CERT_DIR CERT_ROOT "slhdsa" CERT_PATH_SEP + + /* SHA-512 is strong enough for every parameter set; otherwise let the + * library pick the RFC 9814 default (hashOID 0) */ + #if defined(WOLFSSL_SHA512) + #define SLHDSA_TEST_HASH SHA512h + #else + #define SLHDSA_TEST_HASH 0 + #endif + + pkcs7SlhDsaVector vectors[4]; + testSz = 0; + XMEMSET(vectors, 0, sizeof(vectors)); + +#ifndef WOLFSSL_SLHDSA_PARAM_NO_128S + vectors[testSz].certFile = SLHDSA_CERT_DIR "root-slhdsa-shake-128s.der"; + vectors[testSz].keyFile = SLHDSA_CERT_DIR + "root-slhdsa-shake-128s-priv.der"; + vectors[testSz].hashOID = SLHDSA_TEST_HASH; + testSz++; +#endif + +#ifdef WOLFSSL_SLHDSA_SHA2 +#ifndef WOLFSSL_SLHDSA_PARAM_NO_SHA2_128S + vectors[testSz].certFile = SLHDSA_CERT_DIR "root-slhdsa-sha2-128s.der"; + vectors[testSz].keyFile = SLHDSA_CERT_DIR + "root-slhdsa-sha2-128s-priv.der"; + vectors[testSz].hashOID = SLHDSA_TEST_HASH; + testSz++; +#endif +#endif + + /* no-signed-attributes path */ +#ifndef WOLFSSL_SLHDSA_PARAM_NO_128S + vectors[testSz].certFile = SLHDSA_CERT_DIR "root-slhdsa-shake-128s.der"; + vectors[testSz].keyFile = SLHDSA_CERT_DIR + "root-slhdsa-shake-128s-priv.der"; + vectors[testSz].hashOID = SLHDSA_TEST_HASH; + vectors[testSz].noSignedAttribs = 1; + testSz++; +#elif defined(WOLFSSL_SLHDSA_SHA2) && \ + !defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128S) + vectors[testSz].certFile = SLHDSA_CERT_DIR "root-slhdsa-sha2-128s.der"; + vectors[testSz].keyFile = SLHDSA_CERT_DIR + "root-slhdsa-sha2-128s-priv.der"; + vectors[testSz].hashOID = SLHDSA_TEST_HASH; + vectors[testSz].noSignedAttribs = 1; + testSz++; +#endif + + XMEMSET(&rng, 0, sizeof(rng)); + + cert = (byte*)XMALLOC(FOURK_BUF * 3, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + key = (byte*)XMALLOC(FOURK_BUF, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + out = (byte*)XMALLOC(FOURK_BUF * 6, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (cert == NULL || key == NULL || out == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + rngInit = 1; + + if (testSz == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + + for (i = 0; i < testSz; i++) { + f = XFOPEN(vectors[i].certFile, "rb"); + if (f == XBADFILE) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + certSz = (word32)XFREAD(cert, 1, FOURK_BUF * 3, f); + XFCLOSE(f); + f = XBADFILE; + if (certSz == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + + f = XFOPEN(vectors[i].keyFile, "rb"); + if (f == XBADFILE) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + keySz = (word32)XFREAD(key, 1, FOURK_BUF, f); + XFCLOSE(f); + f = XBADFILE; + if (keySz == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + + /* --- encode SignedData --- */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + + ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + + pkcs7->rng = &rng; + pkcs7->content = (byte*)content; + pkcs7->contentSz = (word32)sizeof(content); + pkcs7->contentOID = DATA; + pkcs7->hashOID = vectors[i].hashOID; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + + if (vectors[i].noSignedAttribs) { + ret = wc_PKCS7_SetDefaultSignedAttribs(pkcs7, + WOLFSSL_NO_ATTRIBUTES); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + } + + encodedSz = wc_PKCS7_EncodeSignedData(pkcs7, out, FOURK_BUF * 6); + if (encodedSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(encodedSz), out_lbl); + + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* --- verify SignedData --- */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + + ret = wc_PKCS7_InitWithCert(pkcs7, NULL, 0); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + + ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encodedSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + + if (pkcs7->contentSz != (word32)sizeof(content) || + XMEMCMP(pkcs7->content, content, sizeof(content)) != 0) { + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + } + + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* --- negative: tampered signature must fail --- */ + out[encodedSz - 1] ^= 0xFF; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + + ret = wc_PKCS7_InitWithCert(pkcs7, NULL, 0); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + + ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encodedSz); + if (ret != WC_NO_ERR_TRACE(SIG_VERIFY_E)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + ret = 0; + + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* negative: pre-computed hash must be rejected */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + + ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + + pkcs7->rng = &rng; + pkcs7->content = (byte*)content; + pkcs7->contentSz = (word32)sizeof(content); + pkcs7->contentOID = DATA; + pkcs7->hashOID = SLHDSA_TEST_HASH; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + + XMEMSET(preHash, 0, sizeof(preHash)); + outHeadSz = FOURK_BUF * 6; + outFootSz = FOURK_BUF * 6; + ret = wc_PKCS7_EncodeSignedData_ex(pkcs7, preHash, (word32)sizeof(preHash), + out, &outHeadSz, out, &outFootSz); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + ret = 0; + +out_lbl: + if (f != XBADFILE) + XFCLOSE(f); + if (pkcs7 != NULL) + wc_PKCS7_Free(pkcs7); + if (rngInit) + wc_FreeRng(&rng); + XFREE(cert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + #undef SLHDSA_CERT_DIR + #undef SLHDSA_TEST_HASH + + return ret; +} + +#endif /* WOLFSSL_HAVE_SLHDSA && !VERIFY_ONLY && filesystem */ + + +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) && \ + defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && !defined(NO_SHA256) && \ + !defined(NO_ASN) + +#define SLHDSA_PKCS7_CERT_SZ 2048 +#define SLHDSA_PKCS7_KEY_SZ 256 +#define SLHDSA_PKCS7_OUT_SZ (WC_SLHDSA_MAX_SIG_LEN + 4096) + +/* Issue a certificate for an SLH-DSA public key, signed by an ECC key so that + * only the SignedData itself needs an SLH-DSA signature. */ +static wc_test_ret_t pkcs7_slhdsa_make_cert(SlhDsaKey* slhdsa, int keyType, + ecc_key* ca, WC_RNG* rng, byte* der, int* derSz) +{ + wc_test_ret_t ret; + WC_DECLARE_VAR(cert, Cert, 1, HEAP_HINT); + + WC_ALLOC_VAR_EX(cert, Cert, 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER, + return WC_TEST_RET_ENC_EC(MEMORY_E)); + + ret = wc_InitCert_ex(cert, HEAP_HINT, devId); + if (ret == 0) { + XSTRNCPY(cert->issuer.commonName, "PKCS7 SLH-DSA issuer", + CTC_NAME_SIZE); + XSTRNCPY(cert->subject.commonName, "PKCS7 SLH-DSA signer", + CTC_NAME_SIZE); + cert->sigType = CTC_SHA256wECDSA; + ret = wc_MakeCert_ex(cert, der, SLHDSA_PKCS7_CERT_SZ, keyType, + slhdsa, rng); + } + if (ret > 0) { + ret = wc_SignCert_ex(ret, cert->sigType, der, SLHDSA_PKCS7_CERT_SZ, + ECC_TYPE, ca, rng); + } + if (ret > 0) { + *derSz = (int)ret; + ret = 0; + } + else { + ret = WC_TEST_RET_ENC_EC(ret); + } + + WC_FREE_VAR_EX(cert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* Encode SignedData with the given SLH-DSA signer. hashOID 0 selects the + * RFC 9814 default. Returns encoded size, or negative error from encode. */ +static int pkcs7_slhdsa_encode(byte* cert, word32 certSz, byte* key, + word32 keySz, int hashOID, int noAttribs, WC_RNG* rng, byte* out, + word32 outSz) +{ + static const byte content[] = "RFC 9814 SLH-DSA SignedData"; + wc_PKCS7* pkcs7; + int ret; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + return MEMORY_E; + + ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz); + if (ret == 0) { + pkcs7->rng = rng; + pkcs7->content = (byte*)content; + pkcs7->contentSz = (word32)sizeof(content); + pkcs7->contentOID = DATA; + pkcs7->hashOID = hashOID; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + if (noAttribs) { + ret = wc_PKCS7_SetDefaultSignedAttribs(pkcs7, + WOLFSSL_NO_ATTRIBUTES); + } + } + if (ret == 0) + ret = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz); + + wc_PKCS7_Free(pkcs7); + return ret; +} + +/* Verify SignedData, returning the verify result and the digestAlgorithm the + * signer used. */ +static int pkcs7_slhdsa_verify(byte* in, word32 inSz, int* hashOID) +{ + wc_PKCS7* pkcs7; + int ret; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + return MEMORY_E; + + ret = wc_PKCS7_InitWithCert(pkcs7, NULL, 0); + if (ret == 0) + ret = wc_PKCS7_VerifySignedData(pkcs7, in, inSz); + if (ret == 0 && hashOID != NULL) + *hashOID = pkcs7->hashOID; + + wc_PKCS7_Free(pkcs7); + return ret; +} + +/* Sign and verify SignedData with each fast ("f") SLH-DSA parameter set, + * checking the RFC 9814 digestAlgorithm rules. The small ("s") sets take + * seconds per signature and use the same code paths, so they are covered by + * the 128s certificates in pkcs7signed_slhdsa_test() only. */ +static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) +{ + static const struct { + enum SlhDsaParam param; + int keyType; + int defHashOID; + int weakHashOID; + byte oidLastByte; + } params[] = { +#ifdef WOLFSSL_SLHDSA_PARAM_128F + #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE128) + { SLHDSA_SHAKE128F, SLH_DSA_SHAKE_128F_TYPE, + SHAKE128h, 0, 27 }, + #else + { SLHDSA_SHAKE128F, SLH_DSA_SHAKE_128F_TYPE, + SHA256h, 0, 27 }, + #endif +#endif +#if defined(WOLFSSL_SHA512) + #ifdef WOLFSSL_SLHDSA_PARAM_192F + #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) + { SLHDSA_SHAKE192F, SLH_DSA_SHAKE_192F_TYPE, + SHAKE256h, SHA256h, 29 }, + #else + { SLHDSA_SHAKE192F, SLH_DSA_SHAKE_192F_TYPE, + SHA512h, SHA256h, 29 }, + #endif + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_256F + #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) + { SLHDSA_SHAKE256F, SLH_DSA_SHAKE_256F_TYPE, + SHAKE256h, SHA256h, 31 }, + #else + { SLHDSA_SHAKE256F, SLH_DSA_SHAKE_256F_TYPE, + SHA512h, SHA256h, 31 }, + #endif + #endif +#endif +#ifdef WOLFSSL_SLHDSA_SHA2 + #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_128F + { SLHDSA_SHA2_128F, SLH_DSA_SHA2_128F_TYPE, + SHA256h, 0, 21 }, + #endif + #if defined(WOLFSSL_SHA512) + #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_192F + { SLHDSA_SHA2_192F, SLH_DSA_SHA2_192F_TYPE, + SHA512h, SHA256h, 23 }, + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_SHA2_256F + { SLHDSA_SHA2_256F, SLH_DSA_SHA2_256F_TYPE, + SHA512h, SHA256h, 25 }, + #endif + #endif +#endif + /* keep the array non-empty when no fast set is built */ + { (enum SlhDsaParam)0, 0, 0, 0, 0 } + }; + /* DER of the id-slh-dsa-* OID arc 2.16.840.1.101.3.4.3, without the last + * arc */ + static const byte slhDsaOidPrefix[] = { + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03 + }; + wc_test_ret_t ret = 0; + WC_RNG rng; + int rngInit = 0; + WC_DECLARE_VAR(ca, ecc_key, 1, HEAP_HINT); + int caInit = 0; + SlhDsaKey* slhdsa = NULL; + byte* cert = NULL; + byte* key = NULL; + byte* out = NULL; + int certSz = 0; + int keySz; + int outSz; + int hashOID; + int i, j; + int found; + + WC_ALLOC_VAR_EX(ca, ecc_key, 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER, + return WC_TEST_RET_ENC_EC(MEMORY_E)); + + slhdsa = (SlhDsaKey*)XMALLOC(sizeof(SlhDsaKey), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + cert = (byte*)XMALLOC(SLHDSA_PKCS7_CERT_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + key = (byte*)XMALLOC(SLHDSA_PKCS7_KEY_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + out = (byte*)XMALLOC(SLHDSA_PKCS7_OUT_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (slhdsa == NULL || cert == NULL || key == NULL || out == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + rngInit = 1; + + ret = wc_ecc_init_ex(ca, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + caInit = 1; + ret = wc_ecc_make_key(&rng, 32, ca); +#if defined(WOLFSSL_ASYNC_CRYPT) + ret = wc_AsyncWait(ret, &ca->asyncDev, WC_ASYNC_FLAG_NONE); +#endif + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + + for (i = 0; i < (int)(sizeof(params) / sizeof(params[0])) - 1; i++) { + ret = wc_SlhDsaKey_Init(slhdsa, params[i].param, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + ret = wc_SlhDsaKey_MakeKey(slhdsa, &rng); + if (ret == 0) + ret = pkcs7_slhdsa_make_cert(slhdsa, params[i].keyType, ca, &rng, + cert, &certSz); + keySz = 0; + if (ret == 0) { + keySz = wc_SlhDsaKey_PrivateKeyToDer(slhdsa, key, + SLHDSA_PKCS7_KEY_SZ); + if (keySz <= 0) + ret = WC_TEST_RET_ENC_EC(keySz); + } + wc_SlhDsaKey_Free(slhdsa); + if (ret != 0) + ERROR_OUT(ret, out_lbl); + + /* hashOID 0: digestAlgorithm defaults per RFC 9814 Section 4 */ + outSz = pkcs7_slhdsa_encode(cert, (word32)certSz, key, (word32)keySz, + 0, 0, &rng, out, SLHDSA_PKCS7_OUT_SZ); + if (outSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + hashOID = 0; + ret = pkcs7_slhdsa_verify(out, (word32)outSz, &hashOID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + if (hashOID != params[i].defHashOID) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + + /* signatureAlgorithm naming a different parameter set than the + * signer's key must not verify. The signer's certificate also carries + * the OID, so change the last occurrence (in the SignerInfo). */ + found = -1; + for (j = outSz - (int)sizeof(slhDsaOidPrefix) - 1; j >= 0; j--) { + if (XMEMCMP(out + j, slhDsaOidPrefix, + sizeof(slhDsaOidPrefix)) == 0) { + found = j + (int)sizeof(slhDsaOidPrefix); + break; + } + } + if (found < 0 || out[found] != params[i].oidLastByte) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + out[found] ^= 0x01; /* s <-> f of the same family and level */ + ret = pkcs7_slhdsa_verify(out, (word32)outSz, NULL); + if (ret == 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + + /* without signed attributes the content is signed directly */ + outSz = pkcs7_slhdsa_encode(cert, (word32)certSz, key, (word32)keySz, + 0, 1, &rng, out, SLHDSA_PKCS7_OUT_SZ); + if (outSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + ret = pkcs7_slhdsa_verify(out, (word32)outSz, NULL); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + + if (params[i].weakHashOID != 0) { + /* a digest shorter than 2n must be rejected when signed + * attributes are present... */ + outSz = pkcs7_slhdsa_encode(cert, (word32)certSz, key, + (word32)keySz, params[i].weakHashOID, 0, &rng, out, + SLHDSA_PKCS7_OUT_SZ); + if (outSz != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + + /* ...but is allowed without them, as it is not used to pre-hash + * the message */ + outSz = pkcs7_slhdsa_encode(cert, (word32)certSz, key, + (word32)keySz, params[i].weakHashOID, 1, &rng, out, + SLHDSA_PKCS7_OUT_SZ); + if (outSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + ret = pkcs7_slhdsa_verify(out, (word32)outSz, NULL); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + } + } + ret = 0; + +out_lbl: + if (caInit) + wc_ecc_free(ca); + if (rngInit) + wc_FreeRng(&rng); + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(cert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(slhdsa, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR_EX(ca, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +#undef SLHDSA_PKCS7_CERT_SZ +#undef SLHDSA_PKCS7_KEY_SZ +#undef SLHDSA_PKCS7_OUT_SZ + +#endif /* WOLFSSL_HAVE_SLHDSA && !VERIFY_ONLY && CERT_GEN && HAVE_ECC */ + + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7signed_test(void) { wc_test_ret_t ret = 0; @@ -91891,6 +92530,24 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7signed_test(void) ret = pkcs7signed_mldsa_test(); #endif +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(NO_FILESYSTEM) && \ + !defined(NO_ASN) + if (ret >= 0) + ret = pkcs7signed_slhdsa_interop_test(); +#endif +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) && \ + !defined(NO_FILESYSTEM) && !defined(NO_ASN) + if (ret >= 0) + ret = pkcs7signed_slhdsa_test(); +#endif + +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) && \ + defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && !defined(NO_SHA256) && \ + !defined(NO_ASN) + if (ret >= 0) + ret = pkcs7signed_slhdsa_params_test(); +#endif + XFREE(rsaClientCertBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaClientPrivKeyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaServerCertBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); From 02dd4b63160fb1d62a19fd8434628d13169d7893 Mon Sep 17 00:00:00 2001 From: Duc Anh Luu Date: Fri, 6 Nov 2026 06:53:50 +0700 Subject: [PATCH 2/4] PKCS#7 SLH-DSA: free partially initialised keys, reject sigAlg params 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. --- certs/slhdsa/gen-slhdsa-mldsa-certs.sh | 8 +- wolfcrypt/src/pkcs7.c | 25 +++++- wolfcrypt/test/test.c | 103 ++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 7 deletions(-) diff --git a/certs/slhdsa/gen-slhdsa-mldsa-certs.sh b/certs/slhdsa/gen-slhdsa-mldsa-certs.sh index a8b27bdd6de..43770d13da6 100755 --- a/certs/slhdsa/gen-slhdsa-mldsa-certs.sh +++ b/certs/slhdsa/gen-slhdsa-mldsa-certs.sh @@ -243,10 +243,10 @@ check_result $? "Export ${BENCH} public key" tail -c 32 "${BENCH}_pub.der" > "${BENCH}_pub.bin" rm -f "${BENCH}_pub.der" -# RFC 9814 SignedData interop vectors, verified by pkcs7signed_slhdsa_test() -# in wolfcrypt/test/test.c. One per hash family, covering both the -# signed-attributes and the no-signed-attributes form. OpenSSL picks the -# RFC 9814 digestAlgorithm for the parameter set. +# RFC 9814 SignedData interop vectors, verified by +# pkcs7signed_slhdsa_interop_test() in wolfcrypt/test/test.c. One per hash +# family, covering both the signed-attributes and the no-signed-attributes +# form. OpenSSL picks the RFC 9814 digestAlgorithm for the parameter set. printf 'RFC 9814 SLH-DSA SignedData from OpenSSL' > cms-content.tmp openssl cms -sign -binary -nodetach -outform DER -in cms-content.tmp \ -signer root-slhdsa-shake-128s.pem -inkey root-slhdsa-shake-128s-priv.pem \ diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 7bcf1539903..c241f1800a1 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -1369,6 +1369,10 @@ static int wc_PKCS7_CheckPublicKeyDer(wc_PKCS7* pkcs7, int keyOID, ret = MEMORY_E; break; } + /* Zeroed so that wc_SlhDsaKey_Free() is safe whether or not + * 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); if (ret == 0) { ret = wc_SlhDsaKey_Init(slhdsa, param, pkcs7->heap, @@ -1377,8 +1381,8 @@ static int wc_PKCS7_CheckPublicKeyDer(wc_PKCS7* pkcs7, int keyOID, if (ret == 0) { ret = wc_SlhDsaKey_PublicKeyDecode(key, &scratch, slhdsa, keySz); - wc_SlhDsaKey_Free(slhdsa); } + wc_SlhDsaKey_Free(slhdsa); XFREE(slhdsa, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); break; } @@ -3387,6 +3391,9 @@ static int wc_PKCS7_SlhDsaSign(wc_PKCS7* pkcs7, const byte* msg, word32 msgSz, if (key == NULL) { return MEMORY_E; } + /* Zeroed so that wc_SlhDsaKey_Free() also cleans up after a failed + * wc_SlhDsaKey_Init(). */ + XMEMSET(key, 0, sizeof(*key)); ret = wc_SlhDsaKey_Init(key, param, pkcs7->heap, pkcs7->devId); if (ret == 0) { @@ -3400,8 +3407,8 @@ static int wc_PKCS7_SlhDsaSign(wc_PKCS7* pkcs7, const byte* msg, word32 msgSz, esd->encContentDigest, &sigSz, pkcs7->rng); } - wc_SlhDsaKey_Free(key); } + wc_SlhDsaKey_Free(key); XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); if (ret == 0) { @@ -6177,6 +6184,10 @@ static int wc_PKCS7_SlhDsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, return MEMORY_E; } + /* Zeroed so that wc_SlhDsaKey_Free() also cleans up after a failed + * wc_SlhDsaKey_Init(). */ + XMEMSET(key, 0, sizeof(*key)); + WC_ALLOC_VAR_EX(dCert, DecodedCert, 1, pkcs7->heap, DYNAMIC_TYPE_DCERT, { XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); return MEMORY_E; }); @@ -6191,6 +6202,8 @@ static int wc_PKCS7_SlhDsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, ret = wc_SlhDsaKey_Init(key, param, pkcs7->heap, pkcs7->devId); if (ret != 0) { + /* Init may fail after setting up a hash object */ + wc_SlhDsaKey_Free(key); XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); WC_FREE_VAR_EX(dCert, pkcs7->heap, DYNAMIC_TYPE_DCERT); return ret; @@ -7216,6 +7229,14 @@ static int wc_PKCS7_ParseSignerInfo(wc_PKCS7* pkcs7, byte* in, word32 inSz, GetLength(in, &idx, ¶mLen, inSz) < 0) { ret = ASN_PARSE_E; } +#ifdef WC_PKCS7_HAVE_SLHDSA + /* RFC 9814: the parameters of an id-slh-dsa-* + * signatureAlgorithm MUST be absent. */ + else if (wc_IsSlhDsaOid((int)sigOID)) { + WOLFSSL_MSG("SLH-DSA signature algorithm has params"); + ret = ASN_PARSE_E; + } +#endif else { #if defined(WC_RSA_PSS) && !defined(NO_RSA) if ((word32)sigOID == (word32)CTC_RSASSAPSS && diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 0c30f396a98..e50e08e1525 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -92099,6 +92099,86 @@ static int pkcs7_slhdsa_verify(byte* in, word32 inSz, int* hashOID) return ret; } +/* Insert insSz bytes at offset pos of the DER encoding in der, growing the + * length of every constructed TLV whose contents end at or contain pos. Each + * of those lengths must keep the size of its encoding. Used to build + * otherwise valid SignedData that carries signatureAlgorithm parameters. */ +static int pkcs7_der_insert(byte* der, word32* derSz, word32 maxSz, word32 pos, + const byte* ins, word32 insSz) +{ + word32 lenOff[8]; + word32 lenBytes[8]; + word32 lenVal[8]; + int depth = 0; + word32 idx = 0; + word32 end = *derSz; + int d; + word32 k; + + if (pos > *derSz || *derSz + insSz > maxSz) + return BUFFER_E; + + while (idx < end) { + byte tag; + word32 hdr = 2; + word32 len; + word32 cstart; + + if (idx + 2 > end) + return ASN_PARSE_E; + tag = der[idx]; + len = der[idx + 1]; + if (len & 0x80) { + word32 n = len & 0x7f; + if (n == 0 || n > 3 || idx + 2 + n > end) + return ASN_PARSE_E; + len = 0; + for (k = 0; k < n; k++) + len = (len << 8) | der[idx + 2 + k]; + hdr += n; + } + cstart = idx + hdr; + if (cstart + len > end) + return ASN_PARSE_E; + + if ((tag & ASN_CONSTRUCTED) && cstart <= pos && pos <= cstart + len) { + if (depth == (int)(sizeof(lenOff) / sizeof(lenOff[0]))) + return ASN_PARSE_E; + lenOff[depth] = idx + 1; + lenBytes[depth] = hdr - 1; + lenVal[depth] = len; + depth++; + idx = cstart; + end = cstart + len; + } + else { + idx = cstart + len; + } + } + + /* Rewrite the lengths in place; their encoded size must not change. */ + for (d = 0; d < depth; d++) { + word32 len = lenVal[d] + insSz; + if (lenBytes[d] == 1) { + if (len >= 0x80) + return BUFFER_E; + der[lenOff[d]] = (byte)len; + } + else { + word32 n = lenBytes[d] - 1; + if (n < 3 && (len >> (8 * n)) != 0) + return BUFFER_E; + for (k = 0; k < n; k++) + der[lenOff[d] + n - k] = (byte)(len >> (8 * k)); + } + } + + XMEMMOVE(der + pos + insSz, der + pos, *derSz - pos); + XMEMCPY(der + pos, ins, insSz); + *derSz += insSz; + return 0; +} + /* Sign and verify SignedData with each fast ("f") SLH-DSA parameter set, * checking the RFC 9814 digestAlgorithm rules. The small ("s") sets take * seconds per signature and use the same code paths, so they are covered by @@ -92194,6 +92274,7 @@ static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) DYNAMIC_TYPE_TMP_BUFFER); if (slhdsa == NULL || cert == NULL || key == NULL || out == NULL) ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + XMEMSET(slhdsa, 0, sizeof(*slhdsa)); ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); if (ret != 0) @@ -92213,8 +92294,11 @@ static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) for (i = 0; i < (int)(sizeof(params) / sizeof(params[0])) - 1; i++) { ret = wc_SlhDsaKey_Init(slhdsa, params[i].param, HEAP_HINT, devId); - if (ret != 0) + if (ret != 0) { + /* Init may fail after setting up a hash object */ + wc_SlhDsaKey_Free(slhdsa); ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + } ret = wc_SlhDsaKey_MakeKey(slhdsa, &rng); if (ret == 0) ret = pkcs7_slhdsa_make_cert(slhdsa, params[i].keyType, ca, &rng, @@ -92259,6 +92343,23 @@ static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) ret = pkcs7_slhdsa_verify(out, (word32)outSz, NULL); if (ret == 0) ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + out[found] ^= 0x01; + + /* RFC 9814: the signatureAlgorithm parameters MUST be absent. The + * field is not covered by the signature, so add NULL parameters after + * the OID and check that the otherwise valid SignedData is rejected. */ + { + static const byte asnNull[] = { 0x05, 0x00 }; + word32 nullSz = (word32)outSz; + + ret = pkcs7_der_insert(out, &nullSz, SLHDSA_PKCS7_OUT_SZ, + (word32)found + 1, asnNull, sizeof(asnNull)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + ret = pkcs7_slhdsa_verify(out, nullSz, NULL); + if (ret == 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); + } /* without signed attributes the content is signed directly */ outSz = pkcs7_slhdsa_encode(cert, (word32)certSz, key, (word32)keySz, From 9a32597bf32c9ba2eb88a58b1c0f76d0964a484b Mon Sep 17 00:00:00 2001 From: Duc Anh Luu Date: Tue, 10 Nov 2026 16:40:25 +0700 Subject: [PATCH 3/4] PKCS#7 SLH-DSA: address review comments 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. --- configure.ac | 12 +++ doc/dox_comments/header_files/pkcs7.h | 9 +- wolfcrypt/src/pkcs7.c | 148 ++++++++++++-------------- wolfcrypt/test/test.c | 80 ++++---------- 4 files changed, 110 insertions(+), 139 deletions(-) diff --git a/configure.ac b/configure.ac index eed9b8bd8e9..973db7c7f40 100644 --- a/configure.ac +++ b/configure.ac @@ -13925,6 +13925,18 @@ then ENABLED_X963KDF="yes" AM_CFLAGS="$AM_CFLAGS -DHAVE_X963_KDF" fi + # RFC 9814: SHAKE128 is the MUST-support digestAlgorithm for the SLH-DSA + # SHAKE-128 parameter sets. SHA-3 is already forced on for SLH-DSA. + if test "$ENABLED_SLHDSA" != "no" && + (test "$SLHDSA_PARAM_128S" = "yes" || + test "$SLHDSA_PARAM_128F" = "yes") && + (test "$ENABLED_SHAKE128" = "no" || test "$ENABLED_SHAKE128" = "") && + (test "$ENABLED_FIPS" = "no" || test $HAVE_FIPS_VERSION -ge 6) + then + ENABLED_SHAKE128="yes" + AM_CFLAGS=$(echo "$AM_CFLAGS" | sed 's/-DWOLFSSL_NO_SHAKE128//g') + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_SHAKE128" + fi fi if test "x$ENABLED_DES3" = "xno" diff --git a/doc/dox_comments/header_files/pkcs7.h b/doc/dox_comments/header_files/pkcs7.h index 09ac05672ac..c8ca0a0b203 100644 --- a/doc/dox_comments/header_files/pkcs7.h +++ b/doc/dox_comments/header_files/pkcs7.h @@ -149,11 +149,18 @@ int wc_PKCS7_EncodeData(wc_PKCS7* pkcs7, byte* output, the PKCS7 structure into a buffer containing a parsable PKCS7 signed data packet. For RSA-PSS signers (WC_RSA_PSS), see \ref PKCS7_RSA_PSS. + For an SLH-DSA signer (RFC 9814), pkcs7->hashOID may be left at 0. It is + then set to the digestAlgorithm RFC 9814 requires for the parameter set + (SHA-256 or SHA-512 for the SHA2 sets, SHAKE128 or SHAKE256 for the SHAKE + sets) and keeps that value after the call. With signed attributes, the + digest must be at least twice the SLH-DSA tree hash size (2n). + \return Success On successfully encoding the PKCS7 data into the buffer, returns the index parsed up to in the PKCS7 structure. This index also corresponds to the bytes written to the output buffer. \return BAD_FUNC_ARG Returned if the PKCS7 structure is missing one or - more required elements to generate a signed data packet + more required elements to generate a signed data packet, or if the + digestAlgorithm is too short for an SLH-DSA signer \return MEMORY_E Returned if there is an error allocating memory \return PUBLIC_KEY_E Returned if there is an error parsing the public key \return RSA_BUFFER_E Returned if buffer error, output too small or input diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index c241f1800a1..195addbeb70 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -3159,17 +3159,17 @@ static int wc_PKCS7_BuildPureSigMessage(wc_PKCS7* pkcs7, const byte* attribs, } else { /* No signed attributes: the signature is over the eContent directly, - * which must be present. ML-DSA "pure" Sign/Verify require a non-NULL - * message pointer (a zero-length eContent is still passed by pointer), - * so a missing eContent with no signed attributes is rejected here - * rather than failing later with the same error. + * which must be present. ML-DSA and SLH-DSA "pure" Sign/Verify require + * a non-NULL message pointer (a zero-length eContent is still passed + * by pointer), so a missing eContent with no signed attributes is + * rejected here rather than failing later with the same error. * * Scope: the eContent is signed verbatim. Unlike the RSA/ECC digest * path (wc_PKCS7_SignedDataGetDigest), this does not strip the ASN.1 * tag/length for legacy PKCS#7 ANY-typed content (contentIsPkcs7Type - * == 1). Only CMS (OCTET STRING) eContent is supported for the ML-DSA - * no-signed-attributes path; the common RFC 9882 case carries signed - * attributes and is unaffected. */ + * == 1). Only CMS (OCTET STRING) eContent is supported for the + * ML-DSA/SLH-DSA no-signed-attributes path; the common RFC 9882 and + * RFC 9814 case carries signed attributes and is unaffected. */ if (pkcs7->content == NULL) { return BAD_FUNC_ARG; } @@ -3247,18 +3247,11 @@ static int wc_PKCS7_MlDsaSign(wc_PKCS7* pkcs7, const byte* msg, word32 msgSz, #ifdef WC_PKCS7_HAVE_SLHDSA -/* - * SLH-DSA (FIPS 205) SignedData support, per RFC 9814. - * - * Follows the same "pure" signing model as ML-DSA (RFC 9882): the signature - * is computed over the complete message with an empty context string and - * absent signatureAlgorithm parameters. The shared helper - * wc_PKCS7_BuildPureSigMessage() constructs the message to sign/verify. - */ +/* SLH-DSA (FIPS 205) SignedData per RFC 9814: pure mode like ML-DSA, see + * wc_PKCS7_BuildPureSigMessage(). */ -/* Map an SLH-DSA public key OID to its parameter set. - * Returns 0 and sets *param on success, NOT_COMPILED_IN if the parameter set - * is not built, BAD_FUNC_ARG otherwise. */ +/* Returns 0, NOT_COMPILED_IN if the parameter set isn't built, or + * BAD_FUNC_ARG. */ static int wc_PKCS7_SlhDsaParamFromOID(word32 publicKeyOID, enum SlhDsaParam* param) { @@ -3274,12 +3267,8 @@ static int wc_PKCS7_SlhDsaParamFromOID(word32 publicKeyOID, return 0; } -/* RFC 9814 Section 4: digestAlgorithm to use for an SLH-DSA signer when the - * caller did not set pkcs7->hashOID. These are the hash functions that - * receivers MUST support for each parameter set. Falls back to the SHA-2 - * function of the same size when the SHAKE variant is not compiled in. - * - * Returns hash OID, or 0 if none is available. */ +/* RFC 9814 Section 4: MUST-support digestAlgorithm of the parameter set, used + * when pkcs7->hashOID is not set. Returns 0 if it isn't compiled in. */ static int wc_PKCS7_SlhDsaDefaultHashOID(word32 publicKeyOID) { enum SlhDsaParam param; @@ -3293,8 +3282,6 @@ static int wc_PKCS7_SlhDsaDefaultHashOID(word32 publicKeyOID) case SLHDSA_SHAKE128F: #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE128) return SHAKE128h; - #elif !defined(NO_SHA256) - return SHA256h; #else return 0; #endif @@ -3304,8 +3291,6 @@ static int wc_PKCS7_SlhDsaDefaultHashOID(word32 publicKeyOID) case SLHDSA_SHAKE256F: #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) return SHAKE256h; - #elif defined(WOLFSSL_SHA512) - return SHA512h; #else return 0; #endif @@ -3332,12 +3317,8 @@ static int wc_PKCS7_SlhDsaDefaultHashOID(word32 publicKeyOID) } } -/* RFC 9814 Section 4: when signed attributes are present, the digestAlgorithm - * MUST produce a hash value at least twice the size of the hash used in the - * SLH-DSA tree (n). An SLH-DSA public key is 2n bytes long, so compare the - * digest size against the public key size. - * - * Returns 0 if hashOID is strong enough, BAD_FUNC_ARG otherwise. */ +/* RFC 9814 Section 4: with signed attributes the digest must be at least 2n + * bytes, i.e. the public key size. Returns 0 or BAD_FUNC_ARG if too short. */ static int wc_PKCS7_SlhDsaCheckDigest(word32 publicKeyOID, int hashOID) { enum SlhDsaParam param; @@ -3391,9 +3372,6 @@ static int wc_PKCS7_SlhDsaSign(wc_PKCS7* pkcs7, const byte* msg, word32 msgSz, if (key == NULL) { return MEMORY_E; } - /* Zeroed so that wc_SlhDsaKey_Free() also cleans up after a failed - * wc_SlhDsaKey_Init(). */ - XMEMSET(key, 0, sizeof(*key)); ret = wc_SlhDsaKey_Init(key, param, pkcs7->heap, pkcs7->devId); if (ret == 0) { @@ -3599,13 +3577,7 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, word32 msgSz = 0; int msgAlloc = 0; - if (flatSignedAttribsSz > 0) { - ret = wc_PKCS7_SlhDsaCheckDigest(pkcs7->publicKeyOID, - pkcs7->hashOID); - if (ret != 0) { - break; - } - } + /* digest strength was checked in PKCS7_EncodeSigned() */ ret = wc_PKCS7_BuildPureSigMessage(pkcs7, flatSignedAttribs, flatSignedAttribsSz, &msg, &msgSz, &msgAlloc); @@ -3986,21 +3958,15 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, byte signingTime[MAX_TIME_STRING_SZ]; -#ifdef WC_PKCS7_HAVE_SLHDSA - /* RFC 9814 names a digestAlgorithm for each SLH-DSA parameter set, so use - * it when the caller has not chosen one */ - if (pkcs7 != NULL && pkcs7->hashOID == 0 && - pkcs7->sidType != DEGENERATE_SID && - wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { - pkcs7->hashOID = wc_PKCS7_SlhDsaDefaultHashOID(pkcs7->publicKeyOID); - } -#endif - /* hashOID is unused on the degenerate (certs-only) path, so only require - * it when an actual signer is present */ + * it when an actual signer is present. SLH-DSA signers get a default + * below. */ if (pkcs7 == NULL || - (pkcs7->hashOID == 0 && pkcs7->sidType != DEGENERATE_SID) || - outputSz == NULL) { + (pkcs7->hashOID == 0 && pkcs7->sidType != DEGENERATE_SID + #ifdef WC_PKCS7_HAVE_SLHDSA + && !wc_IsSlhDsaOid((int)pkcs7->publicKeyOID) + #endif + ) || outputSz == NULL) { WOLFSSL_MSG("PKCS7 struct / outputSz null, or hashOID is 0"); return BAD_FUNC_ARG; } @@ -4037,9 +4003,9 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, * hash (passing a non-NULL hashBuf) for the detached-over-empty-content * case (pkcs7->detached set with contentSz == 0, e.g. an SCEP CertRep * PENDING/FAILURE). That specific combination is therefore unsupported for - * ML-DSA and returns BAD_FUNC_ARG here, whereas RSA/ECDSA support it. - * Detached-over-non-empty content and embedded ML-DSA SignedData both - * work. */ + * ML-DSA and SLH-DSA and returns BAD_FUNC_ARG here, whereas RSA/ECDSA + * support it. Detached-over-non-empty content and embedded ML-DSA or + * SLH-DSA SignedData both work. */ if (hashBuf != NULL && (pkcs7->publicKeyOID == ML_DSA_44k || pkcs7->publicKeyOID == ML_DSA_65k || @@ -4049,9 +4015,31 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, } #endif #ifdef WC_PKCS7_HAVE_SLHDSA - if (hashBuf != NULL && wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { - WOLFSSL_MSG("Pre-calculated content hash not supported for SLH-DSA"); - return BAD_FUNC_ARG; + if (pkcs7->sidType != DEGENERATE_SID && + wc_IsSlhDsaOid((int)pkcs7->publicKeyOID)) { + if (hashBuf != NULL) { + WOLFSSL_MSG("Pre-calculated content hash not supported for " + "SLH-DSA"); + return BAD_FUNC_ARG; + } + /* RFC 9814 Section 4: default digestAlgorithm per parameter set, and + * at least 2n bytes with signed attributes. Checked before any + * output is written. */ + if (pkcs7->hashOID == 0) { + pkcs7->hashOID = wc_PKCS7_SlhDsaDefaultHashOID( + pkcs7->publicKeyOID); + if (pkcs7->hashOID == 0) { + return BAD_FUNC_ARG; + } + } + if (wc_PKCS7_GetDefaultSignedAttribCount(pkcs7) + + pkcs7->signedAttribsSz > 0) { + ret = wc_PKCS7_SlhDsaCheckDigest(pkcs7->publicKeyOID, + pkcs7->hashOID); + if (ret != 0) { + return ret; + } + } } #endif @@ -4313,8 +4301,8 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, } /* Size and allocate the single signature buffer (one storage path for - * every algorithm). Deterministic-size algorithms (RSA and ML-DSA, when - * no caller hash is supplied) get their exact size from + * every algorithm). Deterministic-size algorithms (RSA, ML-DSA and + * SLH-DSA, when no caller hash is supplied) get their exact size from * wc_PKCS7_GetSignSize() so the SignerInfo lengths can be reserved * before the final signing pass, and the buffer is right-sized. * @@ -4690,11 +4678,12 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, } if (hashBuf == NULL && pkcs7->sidType != DEGENERATE_SID) { - /* Only RSA (PKCS#1 v1.5) and ML-DSA reach this final signing pass. - * ECDSA and RSA-PSS both require a pre-supplied hash and sign earlier - * via the immediate-sign (signNow) path. The signature is now produced - * over the finalized attributes, and the size reserved during the - * sizing pass above is baked into the SignerInfo/SEQUENCE lengths. */ + /* Only RSA (PKCS#1 v1.5), ML-DSA and SLH-DSA reach this final signing + * pass. ECDSA and RSA-PSS both require a pre-supplied hash and sign + * earlier via the immediate-sign (signNow) path. The signature is now + * produced over the finalized attributes, and the size reserved during + * the sizing pass above is baked into the SignerInfo/SEQUENCE + * lengths. */ word32 reservedSigSz = esd->encContentDigestSz; /* Calculate the final hash and encrypt it. */ @@ -6184,10 +6173,6 @@ static int wc_PKCS7_SlhDsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, return MEMORY_E; } - /* Zeroed so that wc_SlhDsaKey_Free() also cleans up after a failed - * wc_SlhDsaKey_Init(). */ - XMEMSET(key, 0, sizeof(*key)); - WC_ALLOC_VAR_EX(dCert, DecodedCert, 1, pkcs7->heap, DYNAMIC_TYPE_DCERT, { XFREE(key, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); return MEMORY_E; }); @@ -6791,11 +6776,16 @@ static int wc_PKCS7_SignedDataVerifySignature(wc_PKCS7* pkcs7, byte* sig, word32 msgSz = 0; int msgAlloc = 0; - if (signedAttribSz > 0 && - wc_PKCS7_SlhDsaCheckDigest(pkcs7->publicKeyOID, - pkcs7->hashOID) != 0) { - ret = SIG_VERIFY_E; - break; + if (signedAttribSz > 0) { + ret = wc_PKCS7_SlhDsaCheckDigest(pkcs7->publicKeyOID, + pkcs7->hashOID); + /* a too weak digestAlgorithm is a verification failure */ + if (ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = SIG_VERIFY_E; + } + if (ret != 0) { + break; + } } ret = wc_PKCS7_BuildPureSigMessage(pkcs7, signedAttrib, signedAttribSz, &msg, &msgSz, diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e50e08e1525..71f06934a5d 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -91768,7 +91768,6 @@ typedef struct { const char* certFile; const char* keyFile; int hashOID; - int noSignedAttribs; } pkcs7SlhDsaVector; static wc_test_ret_t pkcs7signed_slhdsa_test(void) @@ -91801,7 +91800,7 @@ static wc_test_ret_t pkcs7signed_slhdsa_test(void) #define SLHDSA_TEST_HASH 0 #endif - pkcs7SlhDsaVector vectors[4]; + pkcs7SlhDsaVector vectors[2]; testSz = 0; XMEMSET(vectors, 0, sizeof(vectors)); @@ -91823,23 +91822,10 @@ static wc_test_ret_t pkcs7signed_slhdsa_test(void) #endif #endif - /* no-signed-attributes path */ -#ifndef WOLFSSL_SLHDSA_PARAM_NO_128S - vectors[testSz].certFile = SLHDSA_CERT_DIR "root-slhdsa-shake-128s.der"; - vectors[testSz].keyFile = SLHDSA_CERT_DIR - "root-slhdsa-shake-128s-priv.der"; - vectors[testSz].hashOID = SLHDSA_TEST_HASH; - vectors[testSz].noSignedAttribs = 1; - testSz++; -#elif defined(WOLFSSL_SLHDSA_SHA2) && \ - !defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128S) - vectors[testSz].certFile = SLHDSA_CERT_DIR "root-slhdsa-sha2-128s.der"; - vectors[testSz].keyFile = SLHDSA_CERT_DIR - "root-slhdsa-sha2-128s-priv.der"; - vectors[testSz].hashOID = SLHDSA_TEST_HASH; - vectors[testSz].noSignedAttribs = 1; - testSz++; -#endif + /* the signing keys are 128s only; the fast sets are covered by + * pkcs7signed_slhdsa_params_test() */ + if (testSz == 0) + return 0; XMEMSET(&rng, 0, sizeof(rng)); @@ -91854,9 +91840,6 @@ static wc_test_ret_t pkcs7signed_slhdsa_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); rngInit = 1; - if (testSz == 0) - ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); - for (i = 0; i < testSz; i++) { f = XFOPEN(vectors[i].certFile, "rb"); if (f == XBADFILE) @@ -91893,13 +91876,6 @@ static wc_test_ret_t pkcs7signed_slhdsa_test(void) pkcs7->privateKey = key; pkcs7->privateKeySz = keySz; - if (vectors[i].noSignedAttribs) { - ret = wc_PKCS7_SetDefaultSignedAttribs(pkcs7, - WOLFSSL_NO_ATTRIBUTES); - if (ret != 0) - ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); - } - encodedSz = wc_PKCS7_EncodeSignedData(pkcs7, out, FOURK_BUF * 6); if (encodedSz <= 0) ERROR_OUT(WC_TEST_RET_ENC_EC(encodedSz), out_lbl); @@ -92192,33 +92168,19 @@ static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) int weakHashOID; byte oidLastByte; } params[] = { -#ifdef WOLFSSL_SLHDSA_PARAM_128F - #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE128) +#if defined(WOLFSSL_SLHDSA_PARAM_128F) && defined(WOLFSSL_SHA3) && \ + defined(WOLFSSL_SHAKE128) { SLHDSA_SHAKE128F, SLH_DSA_SHAKE_128F_TYPE, SHAKE128h, 0, 27 }, - #else - { SLHDSA_SHAKE128F, SLH_DSA_SHAKE_128F_TYPE, - SHA256h, 0, 27 }, - #endif #endif -#if defined(WOLFSSL_SHA512) +#if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) && !defined(NO_SHA256) #ifdef WOLFSSL_SLHDSA_PARAM_192F - #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) { SLHDSA_SHAKE192F, SLH_DSA_SHAKE_192F_TYPE, SHAKE256h, SHA256h, 29 }, - #else - { SLHDSA_SHAKE192F, SLH_DSA_SHAKE_192F_TYPE, - SHA512h, SHA256h, 29 }, - #endif #endif #ifdef WOLFSSL_SLHDSA_PARAM_256F - #if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) { SLHDSA_SHAKE256F, SLH_DSA_SHAKE_256F_TYPE, SHAKE256h, SHA256h, 31 }, - #else - { SLHDSA_SHAKE256F, SLH_DSA_SHAKE_256F_TYPE, - SHA512h, SHA256h, 31 }, - #endif #endif #endif #ifdef WOLFSSL_SLHDSA_SHA2 @@ -92245,6 +92207,8 @@ static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) static const byte slhDsaOidPrefix[] = { 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03 }; + static const byte asnNull[] = { 0x05, 0x00 }; + word32 nullSz; wc_test_ret_t ret = 0; WC_RNG rng; int rngInit = 0; @@ -92341,25 +92305,23 @@ static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); out[found] ^= 0x01; /* s <-> f of the same family and level */ ret = pkcs7_slhdsa_verify(out, (word32)outSz, NULL); - if (ret == 0) + /* NOT_COMPILED_IN if the other parameter set isn't built */ + if (ret != WC_NO_ERR_TRACE(SIG_VERIFY_E) && + ret != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); out[found] ^= 0x01; /* RFC 9814: the signatureAlgorithm parameters MUST be absent. The * field is not covered by the signature, so add NULL parameters after * the OID and check that the otherwise valid SignedData is rejected. */ - { - static const byte asnNull[] = { 0x05, 0x00 }; - word32 nullSz = (word32)outSz; - - ret = pkcs7_der_insert(out, &nullSz, SLHDSA_PKCS7_OUT_SZ, - (word32)found + 1, asnNull, sizeof(asnNull)); - if (ret != 0) - ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); - ret = pkcs7_slhdsa_verify(out, nullSz, NULL); - if (ret == 0) - ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); - } + nullSz = (word32)outSz; + ret = pkcs7_der_insert(out, &nullSz, SLHDSA_PKCS7_OUT_SZ, + (word32)found + 1, asnNull, sizeof(asnNull)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + ret = pkcs7_slhdsa_verify(out, nullSz, NULL); + if (ret != WC_NO_ERR_TRACE(ASN_PARSE_E)) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); /* without signed attributes the content is signed directly */ outSz = pkcs7_slhdsa_encode(cert, (word32)certSz, key, (word32)keySz, From 389f19932466cce9568b624e426b73ecf7f9e74c Mon Sep 17 00:00:00 2001 From: Duc Anh Luu Date: Wed, 11 Nov 2026 08:14:21 +0700 Subject: [PATCH 4/4] PKCS#7 SLH-DSA: address bot review findings 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. --- wolfcrypt/src/pkcs7.c | 34 ++++++++++++++++++++++------ wolfcrypt/test/test.c | 52 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 195addbeb70..8648b3ab75e 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -1374,13 +1374,20 @@ static int wc_PKCS7_CheckPublicKeyDer(wc_PKCS7* pkcs7, int keyOID, * failed Init set up. */ XMEMSET(slhdsa, 0, sizeof(*slhdsa)); ret = wc_PKCS7_SlhDsaParamFromOID((word32)keyOID, ¶m); - if (ret == 0) { - ret = wc_SlhDsaKey_Init(slhdsa, param, pkcs7->heap, - pkcs7->devId); + if (ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + /* still load certs of parameter sets that aren't built, e.g. + * in a certs-only bundle; using them fails later */ + ret = 0; } - if (ret == 0) { - ret = wc_SlhDsaKey_PublicKeyDecode(key, &scratch, slhdsa, - keySz); + else { + if (ret == 0) { + ret = wc_SlhDsaKey_Init(slhdsa, param, pkcs7->heap, + pkcs7->devId); + } + if (ret == 0) { + ret = wc_SlhDsaKey_PublicKeyDecode(key, &scratch, slhdsa, + keySz); + } } wc_SlhDsaKey_Free(slhdsa); XFREE(slhdsa, pkcs7->heap, DYNAMIC_TYPE_SLHDSA); @@ -6197,8 +6204,13 @@ static int wc_PKCS7_SlhDsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, InitDecodedCert(dCert, pkcs7->cert[i], pkcs7->certSz[i], pkcs7->heap); #ifdef WC_ASN_UNKNOWN_EXT_CB - if (pkcs7->unknownExtCallback != NULL) + if (pkcs7->unknownExtCallback32 != NULL) { + wc_SetUnknownExtCallback32(dCert, pkcs7->unknownExtCallback32); + } + + if (pkcs7->unknownExtCallback != NULL) { wc_SetUnknownExtCallback(dCert, pkcs7->unknownExtCallback); + } #endif ret = ParseCert(dCert, CA_TYPE, NO_VERIFY, 0); @@ -6787,6 +6799,14 @@ static int wc_PKCS7_SignedDataVerifySignature(wc_PKCS7* pkcs7, byte* sig, break; } } + else if (hashBuf != NULL) { + /* the signature is over the eContent and doesn't bind a + * caller-supplied content hash */ + WOLFSSL_MSG("Pre-calculated content hash not supported for " + "SLH-DSA without signed attributes"); + ret = BAD_FUNC_ARG; + break; + } ret = wc_PKCS7_BuildPureSigMessage(pkcs7, signedAttrib, signedAttribSz, &msg, &msgSz, &msgAlloc); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 71f06934a5d..0e35ff01f0c 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -91751,6 +91751,33 @@ static wc_test_ret_t pkcs7signed_slhdsa_interop_test(void) pkcs7 = NULL; } +#if !defined(WOLFSSL_SLHDSA_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128S) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_128S) + /* a cert of a parameter set that isn't built still loads */ + #if !defined(WOLFSSL_SLHDSA_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128S) + f = XFOPEN(CERT_ROOT "slhdsa" CERT_PATH_SEP "root-slhdsa-sha2-128s.der", + "rb"); + #else + f = XFOPEN(CERT_ROOT "slhdsa" CERT_PATH_SEP "root-slhdsa-shake-128s.der", + "rb"); + #endif + if (f == XBADFILE) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + inSz = (word32)XFREAD(in, 1, FOURK_BUF * 6, f); + XFCLOSE(f); + f = XBADFILE; + if (inSz == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl); + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl); + ret = wc_PKCS7_InitWithCert(pkcs7, in, inSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); +#endif + out_lbl: if (f != XBADFILE) XFCLOSE(f); @@ -92075,6 +92102,27 @@ static int pkcs7_slhdsa_verify(byte* in, word32 inSz, int* hashOID) return ret; } +/* Verify SignedData with a caller-supplied content hash. */ +static int pkcs7_slhdsa_verify_hash(byte* in, word32 inSz) +{ + byte hash[WC_SHA256_DIGEST_SIZE]; + wc_PKCS7* pkcs7; + int ret; + + XMEMSET(hash, 0, sizeof(hash)); + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + return MEMORY_E; + + ret = wc_PKCS7_InitWithCert(pkcs7, NULL, 0); + if (ret == 0) + ret = wc_PKCS7_VerifySignedData_ex(pkcs7, hash, sizeof(hash), in, + inSz, NULL, 0); + + wc_PKCS7_Free(pkcs7); + return ret; +} + /* Insert insSz bytes at offset pos of the DER encoding in der, growing the * length of every constructed TLV whose contents end at or contain pos. Each * of those lengths must keep the size of its encoding. Used to build @@ -92331,6 +92379,10 @@ static wc_test_ret_t pkcs7signed_slhdsa_params_test(void) ret = pkcs7_slhdsa_verify(out, (word32)outSz, NULL); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl); + /* which doesn't bind a caller-supplied content hash */ + ret = pkcs7_slhdsa_verify_hash(out, (word32)outSz); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out_lbl); if (params[i].weakHashOID != 0) { /* a digest shorter than 2n must be rejected when signed