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/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 10eba731316..c241f1800a1 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,45 @@ 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 == 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 +2615,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 +2957,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 +3108,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 +3124,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 +3179,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 +3246,180 @@ 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; + } + /* 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) { + 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 +3462,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 +3581,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 +3986,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 +4048,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 +5450,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 +5537,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 +6152,130 @@ 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; + } + + /* 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; }); + + 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->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 +6634,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 +6773,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 +6902,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 +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 75f2d90b683..30e82e02da9 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -90913,6 +90913,746 @@ 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; +} + +/* 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[] = { +#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); + 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); + 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, + 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; @@ -91130,6 +91870,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);