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 00000000000..54326c46127 Binary files /dev/null and b/certs/slhdsa/cms-slhdsa-sha2-128s-noattr.der differ diff --git a/certs/slhdsa/cms-slhdsa-shake-128s-sha224.der b/certs/slhdsa/cms-slhdsa-shake-128s-sha224.der new file mode 100644 index 00000000000..09a96d3fcf3 Binary files /dev/null and b/certs/slhdsa/cms-slhdsa-shake-128s-sha224.der differ diff --git a/certs/slhdsa/cms-slhdsa-shake-128s.der b/certs/slhdsa/cms-slhdsa-shake-128s.der new file mode 100644 index 00000000000..95c62d87b88 Binary files /dev/null and b/certs/slhdsa/cms-slhdsa-shake-128s.der differ diff --git a/certs/slhdsa/gen-slhdsa-mldsa-certs.sh b/certs/slhdsa/gen-slhdsa-mldsa-certs.sh index 469f6a0b210..43770d13da6 100755 --- a/certs/slhdsa/gen-slhdsa-mldsa-certs.sh +++ b/certs/slhdsa/gen-slhdsa-mldsa-certs.sh @@ -243,5 +243,27 @@ 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_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 \ + -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/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 10eba731316..8648b3ab75e 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,52 @@ 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; + } + /* 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 == 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; + } + 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); + break; + } #endif } @@ -2556,6 +2622,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 +2964,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 +3115,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 +3131,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) { @@ -3070,17 +3166,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; } @@ -3090,8 +3186,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 +3253,158 @@ 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 per RFC 9814: pure mode like ML-DSA, see + * wc_PKCS7_BuildPureSigMessage(). */ + +/* 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) +{ + 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: 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; + + 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; + #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; + #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: 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; + 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 +3447,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 +3566,38 @@ 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; + + /* digest strength was checked in PKCS7_EncodeSigned() */ + 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; @@ -3679,10 +3966,14 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, byte signingTime[MAX_TIME_STRING_SZ]; /* 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; } @@ -3719,9 +4010,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 || @@ -3730,6 +4021,34 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, return BAD_FUNC_ARG; } #endif +#ifdef WC_PKCS7_HAVE_SLHDSA + 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 #if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) keyIdSize = wc_HashGetDigestSize(wc_HashTypeConvert(HashIdAlg( @@ -3989,8 +4308,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. * @@ -4366,11 +4685,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. */ @@ -5126,7 +5446,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 +5533,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 +6148,131 @@ 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) { + /* 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; + } + + InitDecodedCert(dCert, pkcs7->cert[i], pkcs7->certSz[i], pkcs7->heap); + +#ifdef WC_ASN_UNKNOWN_EXT_CB + 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); + 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 +6631,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 +6770,56 @@ 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) { + 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; + } + } + 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); + 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 +6912,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; @@ -6716,6 +7239,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 5ce1415395b..0e35ff01f0c 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -91674,6 +91674,760 @@ 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; + } + +#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); + 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; +} 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[2]; + 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 + + /* 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)); + + 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; + + 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; + + 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; +} + +/* 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 + * 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 + * 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[] = { +#if defined(WOLFSSL_SLHDSA_PARAM_128F) && defined(WOLFSSL_SHA3) && \ + defined(WOLFSSL_SHAKE128) + { SLHDSA_SHAKE128F, SLH_DSA_SHAKE_128F_TYPE, + SHAKE128h, 0, 27 }, +#endif +#if defined(WOLFSSL_SHA3) && defined(WOLFSSL_SHAKE256) && !defined(NO_SHA256) + #ifdef WOLFSSL_SLHDSA_PARAM_192F + { SLHDSA_SHAKE192F, SLH_DSA_SHAKE_192F_TYPE, + SHAKE256h, SHA256h, 29 }, + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_256F + { SLHDSA_SHAKE256F, SLH_DSA_SHAKE_256F_TYPE, + SHAKE256h, SHA256h, 31 }, + #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 + }; + static const byte asnNull[] = { 0x05, 0x00 }; + word32 nullSz; + 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); + XMEMSET(slhdsa, 0, sizeof(*slhdsa)); + + 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) { + /* 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, + 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); + /* 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. */ + 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, + 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); + /* 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 + * 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 +92645,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);