From 515551af3af7f9dc51b1b849e5a18e810ec81b30 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 21:26:32 +0000 Subject: [PATCH 1/6] Add post-quantum (ML-DSA, SLH-DSA) signing support for CRLs CRL signature *verification* already supported post-quantum algorithms via the shared ConfirmSignature() path, but CRL *generation* did not: wc_SignCRL_ex rejected anything other than RSA/ECC keys and sized its signature buffer with MAX_ENCODED_CLASSIC_SIG_SZ, far too small for ML-DSA/SLH-DSA signatures. Extend wc_SignCRL_ex() to accept wc_MlDsaKey* and SlhDsaKey* signing keys and size the signature buffer from the key via GetSignatureBufferSz() (the same PQC-aware sizing the certificate-generation path uses). MakeSignature(), AddSignature() and CheckSigTypeForKey() already handle PQC keys, so they are reused unchanged. The existing RSA/ECC caller (wolfSSL_X509_CRL_sign) passes NULL for the new key parameters. Note: the OpenSSL-compat EVP_PKEY layer has no PQC private-key support (wolfSSL_d2i_PrivateKey has no ML-DSA case and WOLFSSL_EVP_PKEY has no PQC member), so PQC CRL signing is exposed through the wolfcrypt-level wc_SignCRL_ex API rather than the EVP wrapper. Add round-trip tests (build + sign + verify via the certificate manager, plus a tampered-signature negative case) covering ML-DSA-44/65/87 and SLH-DSA SHAKE-128s (SHA2-128s when built). A wolfSSL-signed ML-DSA-44 CRL was also verified with OpenSSL 3.5 (verify OK) to confirm DER interoperability. --- src/crl.c | 2 +- tests/api.c | 254 ++++++++++++++++++++++++++++++++- wolfcrypt/src/asn.c | 55 ++++--- wolfssl/wolfcrypt/asn_public.h | 3 +- 4 files changed, 292 insertions(+), 22 deletions(-) diff --git a/src/crl.c b/src/crl.c index 6c242eb6ee5..b1c2e801c7d 100644 --- a/src/crl.c +++ b/src/crl.c @@ -2914,7 +2914,7 @@ int wolfSSL_X509_CRL_sign(WOLFSSL_X509_CRL* crl, WOLFSSL_EVP_PKEY* pkey, */ if (ret == WOLFSSL_SUCCESS) { totalSz = wc_SignCRL_ex(buf, tbsSz, sigType, buf, bufSz, - rsaKey, eccKey, &rng); + rsaKey, eccKey, NULL, NULL, &rng); if (totalSz < 0) { WOLFSSL_MSG("wc_SignCRL_ex failed"); ret = totalSz; diff --git a/tests/api.c b/tests/api.c index 238ea673976..4e275129dbc 100644 --- a/tests/api.c +++ b/tests/api.c @@ -177,6 +177,9 @@ #ifdef WOLFSSL_HAVE_MLDSA #include #endif +#ifdef WOLFSSL_HAVE_SLHDSA + #include +#endif #if defined(WOLFSSL_HAVE_MLKEM) #include #endif @@ -26144,7 +26147,7 @@ static int test_wc_MakeCRL_max_crlnum(void) } if (EXPECT_SUCCESS()) { crlSz = wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wRSA, - crlBuf, (word32)bufSz, &rsaKey, NULL, &rng); + crlBuf, (word32)bufSz, &rsaKey, NULL, NULL, NULL, &rng); ExpectIntGT(crlSz, 0); } @@ -26153,7 +26156,7 @@ static int test_wc_MakeCRL_max_crlnum(void) * paired with an ECDSA OID must return ALGO_ID_E. --- */ if (EXPECT_SUCCESS()) { ExpectIntEQ(wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wECDSA, - crlBuf, (word32)bufSz, &rsaKey, NULL, &rng), + crlBuf, (word32)bufSz, &rsaKey, NULL, NULL, NULL, &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); } @@ -26218,6 +26221,251 @@ static int test_wc_MakeCRL_max_crlnum(void) return EXPECT_RESULT(); } +#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_ASN) && \ + (defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) +/* Build a CRL, sign it with a post-quantum CA key (ML-DSA or SLH-DSA) through + * wc_MakeCRL_ex + wc_SignCRL_ex, then load it via the certificate manager so + * the PQC signature is verified against the issuing CA. Exactly one of + * mldsaKey/slhDsaKey is non-NULL; the other is only referenced as a NULL + * pointer so this compiles whether or not both algorithms are enabled. Also + * confirms a tampered signature is rejected. Returns the EXPECT result. */ +static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, int sigType) +{ + EXPECT_DECLS; + WOLFSSL_CERT_MANAGER* cm = NULL; + DecodedCert caCert; + int caCertInit = 0; + WC_RNG rng; + int rngInit = 0; + byte issuerDer[1024]; + word32 issuerDerSz = 0; + byte* tbsBuf = NULL; + byte* crlBuf = NULL; + int tbsSz = 0; + int crlSz = 0; + int bufSz = 0; + + /* thisUpdate in the past, nextUpdate far in the future so the CRL is + * current whenever the test runs. */ + const byte thisUpdate[] = "260101000000Z"; /* Jan 1, 2026 */ + const byte nextUpdate[] = "350101000000Z"; /* Jan 1, 2035 */ + /* Minimal CRL number for the v2 extension. */ + const byte crlNum[] = { 0x01 }; + + /* Extract the issuer Name (= CA subject) for the CRL's issuer field so the + * verifier can locate this CA by name. subjectRaw lacks the outer SEQUENCE + * tag, so re-wrap it. */ + wc_InitDecodedCert(&caCert, caCertDer, caCertDerSz, NULL); + caCertInit = 1; + ExpectIntEQ(wc_ParseCert(&caCert, CERT_TYPE, 0, NULL), 0); + if (EXPECT_SUCCESS()) { + word32 seqHdrSz = SetSequence((word32)caCert.subjectRawLen, issuerDer); + ExpectIntLE((int)(seqHdrSz + (word32)caCert.subjectRawLen), + (int)sizeof(issuerDer)); + if (EXPECT_SUCCESS()) { + XMEMCPY(issuerDer + seqHdrSz, caCert.subjectRaw, + (size_t)caCert.subjectRawLen); + issuerDerSz = seqHdrSz + (word32)caCert.subjectRawLen; + } + } + + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) + rngInit = 1; + + /* Size, then encode, the TBSCertList. */ + if (EXPECT_SUCCESS()) { + tbsSz = wc_MakeCRL_ex(issuerDer, issuerDerSz, + thisUpdate, ASN_UTC_TIME, nextUpdate, ASN_UTC_TIME, + NULL, crlNum, (word32)sizeof(crlNum), sigType, 2, NULL, 0); + ExpectIntGT(tbsSz, 0); + } + if (EXPECT_SUCCESS()) { + /* Generous room for the (large) PQC signature and ASN.1 wrappers; + * SLH-DSA signatures alone are several KB. */ + bufSz = tbsSz + 32768; + ExpectNotNull(tbsBuf = (byte*)XMALLOC(bufSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(crlBuf = (byte*)XMALLOC(bufSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + } + if (EXPECT_SUCCESS()) { + tbsSz = wc_MakeCRL_ex(issuerDer, issuerDerSz, + thisUpdate, ASN_UTC_TIME, nextUpdate, ASN_UTC_TIME, + NULL, crlNum, (word32)sizeof(crlNum), sigType, 2, + tbsBuf, (word32)bufSz); + ExpectIntGT(tbsSz, 0); + } + + /* Sign the CRL with the post-quantum key. */ + if (EXPECT_SUCCESS()) { + crlSz = wc_SignCRL_ex(tbsBuf, tbsSz, sigType, crlBuf, (word32)bufSz, + NULL, NULL, mldsaKey, slhDsaKey, &rng); + ExpectIntGT(crlSz, 0); + } + + /* Load the issuing CA and verify the freshly signed CRL. */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caCertDer, caCertDerSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerEnableCRL(cm, WOLFSSL_CRL_CHECKALL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCRLBuffer(cm, crlBuf, crlSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + + /* Negative: corrupt the last signature byte; verification must now fail. */ + if (EXPECT_SUCCESS()) { + WOLFSSL_CERT_MANAGER* cm2 = NULL; + crlBuf[crlSz - 1] ^= 0xFF; + ExpectNotNull(cm2 = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm2, caCertDer, + caCertDerSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerEnableCRL(cm2, WOLFSSL_CRL_CHECKALL), + WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_CertManagerLoadCRLBuffer(cm2, crlBuf, crlSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + wolfSSL_CertManagerFree(cm2); + } + + wolfSSL_CertManagerFree(cm); + XFREE(crlBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(tbsBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (rngInit) + wc_FreeRng(&rng); + if (caCertInit) + wc_FreeDecodedCert(&caCert); + return EXPECT_RESULT(); +} +#endif /* CRL gen + (MLDSA | SLHDSA) */ + +/* Sign and verify CRLs with ML-DSA CA keys for all three parameter sets. */ +static int test_wc_SignCRL_mldsa(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_ASN) && defined(WOLFSSL_HAVE_MLDSA) && \ + defined(WOLFSSL_PEM_TO_DER) + static const struct { + const char* certDer; + const char* keyPem; + int sigType; + } cases[] = { + { "./certs/mldsa/mldsa44-cert.der", "./certs/mldsa/mldsa44-key.pem", + CTC_ML_DSA_44 }, + { "./certs/mldsa/mldsa65-cert.der", "./certs/mldsa/mldsa65-key.pem", + CTC_ML_DSA_65 }, + { "./certs/mldsa/mldsa87-cert.der", "./certs/mldsa/mldsa87-key.pem", + CTC_ML_DSA_87 }, + }; + int i; + int n = (int)(sizeof(cases) / sizeof(cases[0])); + + for (i = 0; i < n; i++) { + byte* certDer = NULL; + size_t certDerSz = 0; + byte* keyPem = NULL; + size_t keyPemSz = 0; + byte* keyDer = NULL; + int keyDerSz = 0; + wc_MlDsaKey key; + int keyInit = 0; + word32 idx = 0; + + ExpectIntEQ(load_file(cases[i].certDer, &certDer, &certDerSz), 0); + ExpectIntEQ(load_file(cases[i].keyPem, &keyPem, &keyPemSz), 0); + + /* Convert the PKCS#8 PEM private key to DER. */ + if (EXPECT_SUCCESS()) { + keyDer = (byte*)XMALLOC(keyPemSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(keyDer); + } + if (EXPECT_SUCCESS()) { + keyDerSz = wc_KeyPemToDer(keyPem, (int)keyPemSz, keyDer, + (int)keyPemSz, NULL); + ExpectIntGT(keyDerSz, 0); + } + + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + if (EXPECT_SUCCESS()) + keyInit = 1; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(&key, keyDer, (word32)keyDerSz, + &idx), 0); + + if (EXPECT_SUCCESS()) { + ExpectIntEQ(pqc_crl_sign_verify(certDer, (word32)certDerSz, &key, + NULL, cases[i].sigType), TEST_SUCCESS); + } + + if (keyInit) + wc_MlDsaKey_Free(&key); + XFREE(keyDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(keyPem, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(certDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif + return EXPECT_RESULT(); +} + +/* Sign and verify CRLs with SLH-DSA CA keys (SHA2 and SHAKE 128s roots). */ +static int test_wc_SignCRL_slhdsa(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_ASN) && defined(WOLFSSL_HAVE_SLHDSA) + static const struct { + const char* certDer; + const char* keyDer; + int sigType; + int param; + } cases[] = { + /* SHAKE variants are always built with --enable-slhdsa. */ + { "./certs/slhdsa/root-slhdsa-shake-128s.der", + "./certs/slhdsa/root-slhdsa-shake-128s-priv.der", + CTC_SLH_DSA_SHAKE_128S, SLHDSA_SHAKE128S }, +#ifdef WOLFSSL_SLHDSA_SHA2 + { "./certs/slhdsa/root-slhdsa-sha2-128s.der", + "./certs/slhdsa/root-slhdsa-sha2-128s-priv.der", + CTC_SLH_DSA_SHA2_128S, SLHDSA_SHA2_128S }, +#endif + }; + int i; + int n = (int)(sizeof(cases) / sizeof(cases[0])); + + for (i = 0; i < n; i++) { + byte* certDer = NULL; + size_t certDerSz = 0; + byte* keyDer = NULL; + size_t keyDerSz = 0; + SlhDsaKey key; + int keyInit = 0; + word32 idx = 0; + + ExpectIntEQ(load_file(cases[i].certDer, &certDer, &certDerSz), 0); + ExpectIntEQ(load_file(cases[i].keyDer, &keyDer, &keyDerSz), 0); + + ExpectIntEQ(wc_SlhDsaKey_Init(&key, (enum SlhDsaParam)cases[i].param, + NULL, INVALID_DEVID), 0); + if (EXPECT_SUCCESS()) + keyInit = 1; + ExpectIntEQ(wc_SlhDsaKey_PrivateKeyDecode(keyDer, &idx, &key, + (word32)keyDerSz), 0); + + if (EXPECT_SUCCESS()) { + ExpectIntEQ(pqc_crl_sign_verify(certDer, (word32)certDerSz, NULL, + &key, cases[i].sigType), TEST_SUCCESS); + } + + if (keyInit) + wc_SlhDsaKey_Free(&key); + XFREE(keyDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(certDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif + return EXPECT_RESULT(); +} + static int test_X509_REQ(void) { EXPECT_DECLS; @@ -37208,6 +37456,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_sk_X509_CRL_encode), TEST_DECL(test_wolfSSL_X509_CRL_sign_large), TEST_DECL(test_wc_MakeCRL_max_crlnum), + TEST_DECL(test_wc_SignCRL_mldsa), + TEST_DECL(test_wc_SignCRL_slhdsa), /* OpenSSL X509 REQ API test */ TEST_DECL(test_wolfSSL_d2i_X509_REQ), diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 8b870e3ce0b..929142203bc 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -37578,36 +37578,44 @@ int wc_MakeCRL_ex(const byte* issuerDer, word32 issuerSz, /* Sign a CRL TBS and produce complete CRL DER. * tbsBuf: contains the TBS at the beginning * tbsSz: size of TBS in tbsBuf - * sType: signature type (e.g., CTC_SHA256wRSA) + * sType: signature type (e.g., CTC_SHA256wRSA, CTC_ML_DSA_44) * buf: output buffer for complete CRL. May be the same as tbsBuf. * bufSz: size of output buffer - * rsaKey/eccKey: signing key (one must be non-NULL) + * rsaKey/eccKey/mldsaKey/slhDsaKey: signing key (exactly one must be non-NULL). + * ML-DSA and SLH-DSA produce post-quantum signatures; their (much larger) + * signature buffer is sized from the key rather than assumed classic. * rng: random number generator * * Returns: size of complete CRL on success, negative error on failure */ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, byte* buf, word32 bufSz, - RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng) + RsaKey* rsaKey, ecc_key* eccKey, + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, WC_RNG* rng) { int ret; int sigSz; - word32 sigCap = MAX_ENCODED_CLASSIC_SIG_SZ; + int maxSigSz; + int nKeys = 0; CertSignCtx certSignCtx_lcl; CertSignCtx* certSignCtx = &certSignCtx_lcl; void* heap = NULL; if (tbsBuf == NULL || tbsSz <= 0 || buf == NULL || rng == NULL) return BAD_FUNC_ARG; - if (rsaKey == NULL && eccKey == NULL) - return BAD_FUNC_ARG; - if (rsaKey != NULL && eccKey != NULL) + + /* Exactly one signing key must be supplied. */ + if (rsaKey != NULL) nKeys++; + if (eccKey != NULL) nKeys++; + if (mldsaKey != NULL) nKeys++; + if (slhDsaKey != NULL) nKeys++; + if (nKeys != 1) return BAD_FUNC_ARG; /* The CRL's signatureAlgorithm OID is written from sType while the * signature is produced from the key, so reject a mismatch. */ - ret = CheckSigTypeForKey(sType, rsaKey, eccKey, NULL, NULL, NULL, NULL, - NULL, NULL, NULL); + ret = CheckSigTypeForKey(sType, rsaKey, eccKey, NULL, NULL, NULL, mldsaKey, + slhDsaKey, NULL, NULL); if (ret != 0) { WOLFSSL_MSG("Signature type does not match signing key"); return ret; @@ -37615,17 +37623,24 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, XMEMSET(certSignCtx, 0, sizeof(*certSignCtx)); - heap = GetSigningKeyHeap(rsaKey, eccKey, NULL, NULL, NULL, NULL, NULL, NULL); + heap = GetSigningKeyHeap(rsaKey, eccKey, NULL, NULL, mldsaKey, slhDsaKey, + NULL, NULL); /* Copy TBS to output buffer first */ if ((word32)tbsSz > bufSz) return BUFFER_E; XMEMCPY(buf, tbsBuf, (size_t)tbsSz); - /* Only RSA/ECC keys are accepted above, so the signature is a classic - * (non-PQC) one and fits MAX_ENCODED_CLASSIC_SIG_SZ. */ + /* Size the signature buffer from the key in use so post-quantum + * (ML-DSA/SLH-DSA) signatures, which far exceed a classic signature, get + * enough room. */ + maxSigSz = GetSignatureBufferSz(rsaKey, eccKey, NULL, NULL, NULL, mldsaKey, + slhDsaKey, NULL, NULL); + if (maxSigSz <= 0) + return (maxSigSz < 0) ? maxSigSz : ALGO_ID_E; + #ifndef WOLFSSL_NO_MALLOC - certSignCtx->sig = (byte*)XMALLOC(MAX_ENCODED_CLASSIC_SIG_SZ, heap, + certSignCtx->sig = (byte*)XMALLOC((word32)maxSigSz, heap, DYNAMIC_TYPE_TMP_BUFFER); if (certSignCtx->sig == NULL) return MEMORY_E; @@ -37633,15 +37648,19 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, * uninitialized memory if MakeSignature fails before writing sig. */ certSignCtx->sig[0] = 0; #else - /* Don't claim more capacity than the fixed sig buffer really has. */ - if (sigCap > (word32)sizeof(certSignCtx->sig)) - sigCap = (word32)sizeof(certSignCtx->sig); + /* Without dynamic memory the signature buffer is a fixed array in + * CertSignCtx; reject rather than overflow it. */ + if ((word32)maxSigSz > WOLFSSL_MAX_SIG_SZ) { + WOLFSSL_MSG("Signature larger than fixed CertSignCtx buffer"); + return BUFFER_E; + } #endif /* Create signature */ sigSz = MakeSignature(certSignCtx, buf, (word32)tbsSz, certSignCtx->sig, - sigCap, rsaKey, eccKey, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, rng, (word32)sType, heap); + (word32)maxSigSz, rsaKey, eccKey, NULL, NULL, + NULL, mldsaKey, slhDsaKey, NULL, NULL, rng, + (word32)sType, heap); if (sigSz < 0) { #ifndef WOLFSSL_NO_MALLOC XFREE(certSignCtx->sig, heap, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index d557ac94cd0..c8c3c723af9 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/wolfssl/wolfcrypt/asn_public.h @@ -716,7 +716,8 @@ WOLFSSL_API int wc_MakeCRL_ex(const byte* issuerDer, word32 issuerSz, byte* output, word32 outputSz); WOLFSSL_API int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, byte* buf, word32 bufSz, - RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng); + RsaKey* rsaKey, ecc_key* eccKey, + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, WC_RNG* rng); #endif /* WOLFSSL_CERT_GEN && HAVE_CRL */ WOLFSSL_API int wc_GetDateInfo(const byte* certDate, int certDateSz, From 706fa007d187314772197c2207f09e4f72cab8aa Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 23:04:16 +0000 Subject: [PATCH 2/6] CRL PQC signing: address review feedback - wc_SignCRL_ex: move the new ML-DSA/SLH-DSA key pointers to the end of the parameter list (after rng) so the original RSA/ECC parameter order is preserved instead of being split by the inserted PQC params. Update the prototype and all callers accordingly. - wc_SignCRL_ex: brace the single-statement key-count ifs to match file style. - Tests: size the CRL output buffer from the signing key's actual signature length (wc_MlDsaKey_GetSigLen / wc_SlhDsaKey_SigSize) plus wrapper headroom instead of a fixed 32768 magic number, so it scales to any parameter set. - Tests: add a post-quantum sigType/key mismatch negative case (a classic OID with a PQC key must return ALGO_ID_E). - Tests: assert the exact ASN_CRL_CONFIRM_E on the tampered-signature case rather than just "not success". - Tests: heap-allocate the issuer DER buffer instead of a 1 KB stack array. --- src/crl.c | 2 +- tests/api.c | 63 +++++++++++++++++++++++++--------- wolfcrypt/src/asn.c | 16 +++++---- wolfssl/wolfcrypt/asn_public.h | 4 +-- 4 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/crl.c b/src/crl.c index b1c2e801c7d..976864098e1 100644 --- a/src/crl.c +++ b/src/crl.c @@ -2914,7 +2914,7 @@ int wolfSSL_X509_CRL_sign(WOLFSSL_X509_CRL* crl, WOLFSSL_EVP_PKEY* pkey, */ if (ret == WOLFSSL_SUCCESS) { totalSz = wc_SignCRL_ex(buf, tbsSz, sigType, buf, bufSz, - rsaKey, eccKey, NULL, NULL, &rng); + rsaKey, eccKey, &rng, NULL, NULL); if (totalSz < 0) { WOLFSSL_MSG("wc_SignCRL_ex failed"); ret = totalSz; diff --git a/tests/api.c b/tests/api.c index 4e275129dbc..a48775e7cd4 100644 --- a/tests/api.c +++ b/tests/api.c @@ -26147,7 +26147,7 @@ static int test_wc_MakeCRL_max_crlnum(void) } if (EXPECT_SUCCESS()) { crlSz = wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wRSA, - crlBuf, (word32)bufSz, &rsaKey, NULL, NULL, NULL, &rng); + crlBuf, (word32)bufSz, &rsaKey, NULL, &rng, NULL, NULL); ExpectIntGT(crlSz, 0); } @@ -26156,7 +26156,7 @@ static int test_wc_MakeCRL_max_crlnum(void) * paired with an ECDSA OID must return ALGO_ID_E. --- */ if (EXPECT_SUCCESS()) { ExpectIntEQ(wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wECDSA, - crlBuf, (word32)bufSz, &rsaKey, NULL, NULL, NULL, &rng), + crlBuf, (word32)bufSz, &rsaKey, NULL, &rng, NULL, NULL), WC_NO_ERR_TRACE(ALGO_ID_E)); } @@ -26239,13 +26239,14 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, int caCertInit = 0; WC_RNG rng; int rngInit = 0; - byte issuerDer[1024]; + byte* issuerDer = NULL; word32 issuerDerSz = 0; byte* tbsBuf = NULL; byte* crlBuf = NULL; int tbsSz = 0; int crlSz = 0; int bufSz = 0; + int sigSz = 0; /* thisUpdate in the past, nextUpdate far in the future so the CRL is * current whenever the test runs. */ @@ -26260,15 +26261,16 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, wc_InitDecodedCert(&caCert, caCertDer, caCertDerSz, NULL); caCertInit = 1; ExpectIntEQ(wc_ParseCert(&caCert, CERT_TYPE, 0, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectNotNull(issuerDer = (byte*)XMALLOC( + (size_t)caCert.subjectRawLen + MAX_SEQ_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + } if (EXPECT_SUCCESS()) { word32 seqHdrSz = SetSequence((word32)caCert.subjectRawLen, issuerDer); - ExpectIntLE((int)(seqHdrSz + (word32)caCert.subjectRawLen), - (int)sizeof(issuerDer)); - if (EXPECT_SUCCESS()) { - XMEMCPY(issuerDer + seqHdrSz, caCert.subjectRaw, - (size_t)caCert.subjectRawLen); - issuerDerSz = seqHdrSz + (word32)caCert.subjectRawLen; - } + XMEMCPY(issuerDer + seqHdrSz, caCert.subjectRaw, + (size_t)caCert.subjectRawLen); + issuerDerSz = seqHdrSz + (word32)caCert.subjectRawLen; } ExpectIntEQ(wc_InitRng(&rng), 0); @@ -26282,10 +26284,25 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, NULL, crlNum, (word32)sizeof(crlNum), sigType, 2, NULL, 0); ExpectIntGT(tbsSz, 0); } + /* Size the output from the key's actual signature length (PQC signatures + * range from a few KB for ML-DSA to tens of KB for large SLH-DSA sets) + * plus headroom for the AlgorithmIdentifier, BIT STRING and SEQUENCE + * wrappers, rather than a fixed magic number. */ +#ifdef WOLFSSL_HAVE_MLDSA + if (mldsaKey != NULL) { + int l = 0; + ExpectIntEQ(wc_MlDsaKey_GetSigLen(mldsaKey, &l), 0); + sigSz = l; + } +#endif +#ifdef WOLFSSL_HAVE_SLHDSA + if (slhDsaKey != NULL) { + sigSz = wc_SlhDsaKey_SigSize(slhDsaKey); + } +#endif + ExpectIntGT(sigSz, 0); if (EXPECT_SUCCESS()) { - /* Generous room for the (large) PQC signature and ASN.1 wrappers; - * SLH-DSA signatures alone are several KB. */ - bufSz = tbsSz + 32768; + bufSz = tbsSz + sigSz + 512; ExpectNotNull(tbsBuf = (byte*)XMALLOC(bufSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); ExpectNotNull(crlBuf = (byte*)XMALLOC(bufSz, NULL, @@ -26302,10 +26319,19 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, /* Sign the CRL with the post-quantum key. */ if (EXPECT_SUCCESS()) { crlSz = wc_SignCRL_ex(tbsBuf, tbsSz, sigType, crlBuf, (word32)bufSz, - NULL, NULL, mldsaKey, slhDsaKey, &rng); + NULL, NULL, &rng, mldsaKey, slhDsaKey); ExpectIntGT(crlSz, 0); } + /* Negative: a classic signatureAlgorithm OID must be rejected for a PQC + * key before any signature is produced. CheckSigTypeForKey runs before the + * TBS is copied into the output, so crlBuf still holds the valid CRL. */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wRSA, crlBuf, + (word32)bufSz, NULL, NULL, &rng, mldsaKey, slhDsaKey), + WC_NO_ERR_TRACE(ALGO_ID_E)); + } + /* Load the issuing CA and verify the freshly signed CRL. */ ExpectNotNull(cm = wolfSSL_CertManagerNew()); ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caCertDer, caCertDerSz, @@ -26315,7 +26341,9 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, ExpectIntEQ(wolfSSL_CertManagerLoadCRLBuffer(cm, crlBuf, crlSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - /* Negative: corrupt the last signature byte; verification must now fail. */ + /* Negative: flip a byte of the signature *value*. The DER lengths are + * unchanged so the CRL still parses; only the signature check can reject + * it, which must surface as ASN_CRL_CONFIRM_E. */ if (EXPECT_SUCCESS()) { WOLFSSL_CERT_MANAGER* cm2 = NULL; crlBuf[crlSz - 1] ^= 0xFF; @@ -26324,12 +26352,13 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, caCertDerSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CertManagerEnableCRL(cm2, WOLFSSL_CRL_CHECKALL), WOLFSSL_SUCCESS); - ExpectIntNE(wolfSSL_CertManagerLoadCRLBuffer(cm2, crlBuf, crlSz, - WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCRLBuffer(cm2, crlBuf, crlSz, + WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_CRL_CONFIRM_E)); wolfSSL_CertManagerFree(cm2); } wolfSSL_CertManagerFree(cm); + XFREE(issuerDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(crlBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(tbsBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (rngInit) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 929142203bc..aaae82d1510 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -37583,15 +37583,17 @@ int wc_MakeCRL_ex(const byte* issuerDer, word32 issuerSz, * bufSz: size of output buffer * rsaKey/eccKey/mldsaKey/slhDsaKey: signing key (exactly one must be non-NULL). * ML-DSA and SLH-DSA produce post-quantum signatures; their (much larger) - * signature buffer is sized from the key rather than assumed classic. + * signature buffer is sized from the key rather than assumed classic. The + * PQC key pointers are last so the original RSA/ECC parameter order is + * preserved. * rng: random number generator * * Returns: size of complete CRL on success, negative error on failure */ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, byte* buf, word32 bufSz, - RsaKey* rsaKey, ecc_key* eccKey, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, WC_RNG* rng) + RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey) { int ret; int sigSz; @@ -37605,10 +37607,10 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, return BAD_FUNC_ARG; /* Exactly one signing key must be supplied. */ - if (rsaKey != NULL) nKeys++; - if (eccKey != NULL) nKeys++; - if (mldsaKey != NULL) nKeys++; - if (slhDsaKey != NULL) nKeys++; + if (rsaKey != NULL) { nKeys++; } + if (eccKey != NULL) { nKeys++; } + if (mldsaKey != NULL) { nKeys++; } + if (slhDsaKey != NULL) { nKeys++; } if (nKeys != 1) return BAD_FUNC_ARG; diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index c8c3c723af9..3520bd10786 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/wolfssl/wolfcrypt/asn_public.h @@ -716,8 +716,8 @@ WOLFSSL_API int wc_MakeCRL_ex(const byte* issuerDer, word32 issuerSz, byte* output, word32 outputSz); WOLFSSL_API int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, byte* buf, word32 bufSz, - RsaKey* rsaKey, ecc_key* eccKey, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, WC_RNG* rng); + RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey); #endif /* WOLFSSL_CERT_GEN && HAVE_CRL */ WOLFSSL_API int wc_GetDateInfo(const byte* certDate, int certDateSz, From 5bdb15ca3d455ec755b7593c8ba0f17fc5d6fec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 17 Jul 2026 11:47:41 +0200 Subject: [PATCH 3/6] CRL PQC signing: rework API around keyType/void* Restore wc_SignCRL_ex to its original RSA/ECC signature and add wc_SignCRL_ex2, which takes an untyped key plus a keyType selector like wc_SignCert_ex and wc_MakeCert_ex. wc_SignCRL_ex becomes a thin wrapper that resolves the RSA/ECC key and defers to wc_SignCRL_ex2, so the two entry points share one implementation and the existing public API signature is preserved. In the no-malloc path, keep the BUFFER_E reject consistent with SignCert but size the guard from sizeof(certSignCtx->sig), and note that a no-malloc build signing CRLs with large PQC keys must size WOLFSSL_MAX_SIG_SZ accordingly. Update src/crl.c back to the classic call and adjust the CRL tests to drive wc_SignCRL_ex2 with a keyType. --- src/crl.c | 2 +- tests/api.c | 33 +++++---- wolfcrypt/src/asn.c | 128 +++++++++++++++++++++++++++------ wolfssl/wolfcrypt/asn_public.h | 6 +- 4 files changed, 129 insertions(+), 40 deletions(-) diff --git a/src/crl.c b/src/crl.c index 976864098e1..6c242eb6ee5 100644 --- a/src/crl.c +++ b/src/crl.c @@ -2914,7 +2914,7 @@ int wolfSSL_X509_CRL_sign(WOLFSSL_X509_CRL* crl, WOLFSSL_EVP_PKEY* pkey, */ if (ret == WOLFSSL_SUCCESS) { totalSz = wc_SignCRL_ex(buf, tbsSz, sigType, buf, bufSz, - rsaKey, eccKey, &rng, NULL, NULL); + rsaKey, eccKey, &rng); if (totalSz < 0) { WOLFSSL_MSG("wc_SignCRL_ex failed"); ret = totalSz; diff --git a/tests/api.c b/tests/api.c index a48775e7cd4..86fdc06d9ba 100644 --- a/tests/api.c +++ b/tests/api.c @@ -26147,7 +26147,7 @@ static int test_wc_MakeCRL_max_crlnum(void) } if (EXPECT_SUCCESS()) { crlSz = wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wRSA, - crlBuf, (word32)bufSz, &rsaKey, NULL, &rng, NULL, NULL); + crlBuf, (word32)bufSz, &rsaKey, NULL, &rng); ExpectIntGT(crlSz, 0); } @@ -26156,7 +26156,7 @@ static int test_wc_MakeCRL_max_crlnum(void) * paired with an ECDSA OID must return ALGO_ID_E. --- */ if (EXPECT_SUCCESS()) { ExpectIntEQ(wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wECDSA, - crlBuf, (word32)bufSz, &rsaKey, NULL, &rng, NULL, NULL), + crlBuf, (word32)bufSz, &rsaKey, NULL, &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); } @@ -26231,7 +26231,7 @@ static int test_wc_MakeCRL_max_crlnum(void) * pointer so this compiles whether or not both algorithms are enabled. Also * confirms a tampered signature is rejected. Returns the EXPECT result. */ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, int sigType) + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, int keyType, int sigType) { EXPECT_DECLS; WOLFSSL_CERT_MANAGER* cm = NULL; @@ -26247,6 +26247,9 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, int crlSz = 0; int bufSz = 0; int sigSz = 0; + /* Exactly one of the PQC keys is non-NULL; wc_SignCRL_ex2 takes it as an + * untyped pointer paired with keyType. */ + void* signKey = (mldsaKey != NULL) ? (void*)mldsaKey : (void*)slhDsaKey; /* thisUpdate in the past, nextUpdate far in the future so the CRL is * current whenever the test runs. */ @@ -26318,8 +26321,8 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, /* Sign the CRL with the post-quantum key. */ if (EXPECT_SUCCESS()) { - crlSz = wc_SignCRL_ex(tbsBuf, tbsSz, sigType, crlBuf, (word32)bufSz, - NULL, NULL, &rng, mldsaKey, slhDsaKey); + crlSz = wc_SignCRL_ex2(tbsBuf, tbsSz, sigType, crlBuf, (word32)bufSz, + keyType, signKey, &rng); ExpectIntGT(crlSz, 0); } @@ -26327,8 +26330,8 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, * key before any signature is produced. CheckSigTypeForKey runs before the * TBS is copied into the output, so crlBuf still holds the valid CRL. */ if (EXPECT_SUCCESS()) { - ExpectIntEQ(wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wRSA, crlBuf, - (word32)bufSz, NULL, NULL, &rng, mldsaKey, slhDsaKey), + ExpectIntEQ(wc_SignCRL_ex2(tbsBuf, tbsSz, CTC_SHA256wRSA, crlBuf, + (word32)bufSz, keyType, signKey, &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); } @@ -26379,14 +26382,15 @@ static int test_wc_SignCRL_mldsa(void) static const struct { const char* certDer; const char* keyPem; + int keyType; int sigType; } cases[] = { { "./certs/mldsa/mldsa44-cert.der", "./certs/mldsa/mldsa44-key.pem", - CTC_ML_DSA_44 }, + ML_DSA_44_TYPE, CTC_ML_DSA_44 }, { "./certs/mldsa/mldsa65-cert.der", "./certs/mldsa/mldsa65-key.pem", - CTC_ML_DSA_65 }, + ML_DSA_65_TYPE, CTC_ML_DSA_65 }, { "./certs/mldsa/mldsa87-cert.der", "./certs/mldsa/mldsa87-key.pem", - CTC_ML_DSA_87 }, + ML_DSA_87_TYPE, CTC_ML_DSA_87 }, }; int i; int n = (int)(sizeof(cases) / sizeof(cases[0])); @@ -26424,7 +26428,7 @@ static int test_wc_SignCRL_mldsa(void) if (EXPECT_SUCCESS()) { ExpectIntEQ(pqc_crl_sign_verify(certDer, (word32)certDerSz, &key, - NULL, cases[i].sigType), TEST_SUCCESS); + NULL, cases[i].keyType, cases[i].sigType), TEST_SUCCESS); } if (keyInit) @@ -26446,17 +26450,18 @@ static int test_wc_SignCRL_slhdsa(void) static const struct { const char* certDer; const char* keyDer; + int keyType; int sigType; int param; } cases[] = { /* SHAKE variants are always built with --enable-slhdsa. */ { "./certs/slhdsa/root-slhdsa-shake-128s.der", "./certs/slhdsa/root-slhdsa-shake-128s-priv.der", - CTC_SLH_DSA_SHAKE_128S, SLHDSA_SHAKE128S }, + SLH_DSA_SHAKE_128S_TYPE, CTC_SLH_DSA_SHAKE_128S, SLHDSA_SHAKE128S }, #ifdef WOLFSSL_SLHDSA_SHA2 { "./certs/slhdsa/root-slhdsa-sha2-128s.der", "./certs/slhdsa/root-slhdsa-sha2-128s-priv.der", - CTC_SLH_DSA_SHA2_128S, SLHDSA_SHA2_128S }, + SLH_DSA_SHA2_128S_TYPE, CTC_SLH_DSA_SHA2_128S, SLHDSA_SHA2_128S }, #endif }; int i; @@ -26483,7 +26488,7 @@ static int test_wc_SignCRL_slhdsa(void) if (EXPECT_SUCCESS()) { ExpectIntEQ(pqc_crl_sign_verify(certDer, (word32)certDerSz, NULL, - &key, cases[i].sigType), TEST_SUCCESS); + &key, cases[i].keyType, cases[i].sigType), TEST_SUCCESS); } if (keyInit) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index aaae82d1510..337c506e830 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -37592,32 +37592,112 @@ int wc_MakeCRL_ex(const byte* issuerDer, word32 issuerSz, */ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, byte* buf, word32 bufSz, - RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey) + RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng) +{ + int keyType; + void* key; + + /* Accept exactly one of the RSA/ECC keys, then defer to the any-key + * wc_SignCRL_ex2 so the two entry points share one implementation. */ + if (rsaKey != NULL && eccKey != NULL) + return BAD_FUNC_ARG; + + if (rsaKey != NULL) { + keyType = RSA_TYPE; + key = rsaKey; + } + else if (eccKey != NULL) { + keyType = ECC_TYPE; + key = eccKey; + } + else { + return BAD_FUNC_ARG; + } + + return wc_SignCRL_ex2(tbsBuf, tbsSz, sType, buf, bufSz, keyType, key, rng); +} + +int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, + byte* buf, word32 bufSz, int keyType, void* key, + WC_RNG* rng) { int ret; int sigSz; int maxSigSz; - int nKeys = 0; CertSignCtx certSignCtx_lcl; CertSignCtx* certSignCtx = &certSignCtx_lcl; void* heap = NULL; - - if (tbsBuf == NULL || tbsSz <= 0 || buf == NULL || rng == NULL) + RsaKey* rsaKey = NULL; + ecc_key* eccKey = NULL; + ed25519_key* ed25519Key = NULL; + ed448_key* ed448Key = NULL; + falcon_key* falconKey = NULL; + wc_MlDsaKey* mldsaKey = NULL; + SlhDsaKey* slhDsaKey = NULL; + LmsKey* lmsKey = NULL; + XmssKey* xmssKey = NULL; + + if (tbsBuf == NULL || tbsSz <= 0 || buf == NULL || key == NULL || + rng == NULL) return BAD_FUNC_ARG; - /* Exactly one signing key must be supplied. */ - if (rsaKey != NULL) { nKeys++; } - if (eccKey != NULL) { nKeys++; } - if (mldsaKey != NULL) { nKeys++; } - if (slhDsaKey != NULL) { nKeys++; } - if (nKeys != 1) + /* Resolve the untyped key from keyType, matching wc_SignCert_ex. */ + if (keyType == RSA_TYPE) + rsaKey = (RsaKey*)key; + else if (keyType == ECC_TYPE) + eccKey = (ecc_key*)key; + else if (keyType == ED25519_TYPE) + ed25519Key = (ed25519_key*)key; + else if (keyType == ED448_TYPE) + ed448Key = (ed448_key*)key; + else if (keyType == FALCON_LEVEL1_TYPE) + falconKey = (falcon_key*)key; + else if (keyType == FALCON_LEVEL5_TYPE) + falconKey = (falcon_key*)key; +#ifdef WOLFSSL_MLDSA_FIPS204_DRAFT + else if (keyType == DILITHIUM_LEVEL2_TYPE) + mldsaKey = (wc_MlDsaKey*)key; + else if (keyType == DILITHIUM_LEVEL3_TYPE) + mldsaKey = (wc_MlDsaKey*)key; + else if (keyType == DILITHIUM_LEVEL5_TYPE) + mldsaKey = (wc_MlDsaKey*)key; +#endif + else if (keyType == ML_DSA_44_TYPE) + mldsaKey = (wc_MlDsaKey*)key; + else if (keyType == ML_DSA_65_TYPE) + mldsaKey = (wc_MlDsaKey*)key; + else if (keyType == ML_DSA_87_TYPE) + mldsaKey = (wc_MlDsaKey*)key; +#ifdef WOLFSSL_HAVE_SLHDSA + else if (IsSlhDsaKeyType(keyType)) + slhDsaKey = (SlhDsaKey*)key; +#endif +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + else if (keyType == LMS_TYPE) + lmsKey = (LmsKey*)key; +#endif +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + /* The selector must match the key's actual tree variant so XMSS_TYPE and + * XMSSMT_TYPE are not silently interchangeable. */ + else if (keyType == XMSS_TYPE) { + xmssKey = (XmssKey*)key; + if (xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } + else if (keyType == XMSSMT_TYPE) { + xmssKey = (XmssKey*)key; + if (!xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } +#endif + else { return BAD_FUNC_ARG; + } /* The CRL's signatureAlgorithm OID is written from sType while the * signature is produced from the key, so reject a mismatch. */ - ret = CheckSigTypeForKey(sType, rsaKey, eccKey, NULL, NULL, NULL, mldsaKey, - slhDsaKey, NULL, NULL); + ret = CheckSigTypeForKey(sType, rsaKey, eccKey, ed25519Key, ed448Key, + falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey); if (ret != 0) { WOLFSSL_MSG("Signature type does not match signing key"); return ret; @@ -37625,8 +37705,8 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, XMEMSET(certSignCtx, 0, sizeof(*certSignCtx)); - heap = GetSigningKeyHeap(rsaKey, eccKey, NULL, NULL, mldsaKey, slhDsaKey, - NULL, NULL); + heap = GetSigningKeyHeap(rsaKey, eccKey, ed25519Key, ed448Key, mldsaKey, + slhDsaKey, lmsKey, xmssKey); /* Copy TBS to output buffer first */ if ((word32)tbsSz > bufSz) @@ -37636,8 +37716,8 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, /* Size the signature buffer from the key in use so post-quantum * (ML-DSA/SLH-DSA) signatures, which far exceed a classic signature, get * enough room. */ - maxSigSz = GetSignatureBufferSz(rsaKey, eccKey, NULL, NULL, NULL, mldsaKey, - slhDsaKey, NULL, NULL); + maxSigSz = GetSignatureBufferSz(rsaKey, eccKey, ed25519Key, ed448Key, + falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey); if (maxSigSz <= 0) return (maxSigSz < 0) ? maxSigSz : ALGO_ID_E; @@ -37650,9 +37730,11 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, * uninitialized memory if MakeSignature fails before writing sig. */ certSignCtx->sig[0] = 0; #else - /* Without dynamic memory the signature buffer is a fixed array in - * CertSignCtx; reject rather than overflow it. */ - if ((word32)maxSigSz > WOLFSSL_MAX_SIG_SZ) { + /* Without dynamic memory the signature is written into the fixed + * CertSignCtx.sig array; reject rather than overflow it. A no-malloc build + * that signs CRLs with large (PQC) keys must size WOLFSSL_MAX_SIG_SZ to fit + * that signature. */ + if ((word32)maxSigSz > (word32)sizeof(certSignCtx->sig)) { WOLFSSL_MSG("Signature larger than fixed CertSignCtx buffer"); return BUFFER_E; } @@ -37660,9 +37742,9 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, /* Create signature */ sigSz = MakeSignature(certSignCtx, buf, (word32)tbsSz, certSignCtx->sig, - (word32)maxSigSz, rsaKey, eccKey, NULL, NULL, - NULL, mldsaKey, slhDsaKey, NULL, NULL, rng, - (word32)sType, heap); + (word32)maxSigSz, rsaKey, eccKey, ed25519Key, + ed448Key, falconKey, mldsaKey, slhDsaKey, lmsKey, + xmssKey, rng, (word32)sType, heap); if (sigSz < 0) { #ifndef WOLFSSL_NO_MALLOC XFREE(certSignCtx->sig, heap, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index 3520bd10786..fa6ad32e36c 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/wolfssl/wolfcrypt/asn_public.h @@ -716,8 +716,10 @@ WOLFSSL_API int wc_MakeCRL_ex(const byte* issuerDer, word32 issuerSz, byte* output, word32 outputSz); WOLFSSL_API int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, byte* buf, word32 bufSz, - RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey); + RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng); +WOLFSSL_API int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, + byte* buf, word32 bufSz, int keyType, void* key, + WC_RNG* rng); #endif /* WOLFSSL_CERT_GEN && HAVE_CRL */ WOLFSSL_API int wc_GetDateInfo(const byte* certDate, int certDateSz, From 54a49bd77ea98c698c5258fccd649e812a6d2f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 17 Jul 2026 15:44:42 +0200 Subject: [PATCH 4/6] CRL signing: reject stateful schemes, test more algorithms Address review feedback on the wc_SignCRL_ex2 API. Reject stateful hash-based schemes (LMS/XMSS/XMSSMT) for CRL signing with ALGO_ID_E: a CRL is reissued periodically and would exhaust the key's one-time signature state, with catastrophic reuse risk if that state is mismanaged. Correct the wc_SignCRL_ex doc comment to describe only RSA/ECC, and add a doc comment for wc_SignCRL_ex2. Generalize the CRL sign-and-verify test helper to take a keyType and an untyped key, and add coverage for the algorithms wc_SignCRL_ex2 newly supports: Ed25519, Ed448, and a negative test asserting LMS/XMSS are rejected. --- tests/api.c | 189 ++++++++++++++++++++++++++++++++++---------- wolfcrypt/src/asn.c | 54 +++++++------ 2 files changed, 179 insertions(+), 64 deletions(-) diff --git a/tests/api.c b/tests/api.c index 86fdc06d9ba..4ac01e2860a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -26223,15 +26223,15 @@ static int test_wc_MakeCRL_max_crlnum(void) #if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ !defined(NO_ASN) && \ - (defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) -/* Build a CRL, sign it with a post-quantum CA key (ML-DSA or SLH-DSA) through - * wc_MakeCRL_ex + wc_SignCRL_ex, then load it via the certificate manager so - * the PQC signature is verified against the issuing CA. Exactly one of - * mldsaKey/slhDsaKey is non-NULL; the other is only referenced as a NULL - * pointer so this compiles whether or not both algorithms are enabled. Also - * confirms a tampered signature is rejected. Returns the EXPECT result. */ -static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, int keyType, int sigType) + (defined(HAVE_ED25519) || defined(HAVE_ED448) || \ + defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) +/* Build a CRL, sign it with the given CA key through wc_MakeCRL_ex + + * wc_SignCRL_ex2, then load it via the certificate manager so the signature is + * verified against the issuing CA. keyType selects how key is interpreted. + * Also confirms a tampered signature and a family-mismatched signature type are + * rejected. Returns the EXPECT result. */ +static int crl_sign_verify_ex2(const byte* caCertDer, word32 caCertDerSz, + int keyType, void* key, int sigType) { EXPECT_DECLS; WOLFSSL_CERT_MANAGER* cm = NULL; @@ -26246,10 +26246,6 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, int tbsSz = 0; int crlSz = 0; int bufSz = 0; - int sigSz = 0; - /* Exactly one of the PQC keys is non-NULL; wc_SignCRL_ex2 takes it as an - * untyped pointer paired with keyType. */ - void* signKey = (mldsaKey != NULL) ? (void*)mldsaKey : (void*)slhDsaKey; /* thisUpdate in the past, nextUpdate far in the future so the CRL is * current whenever the test runs. */ @@ -26287,25 +26283,12 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, NULL, crlNum, (word32)sizeof(crlNum), sigType, 2, NULL, 0); ExpectIntGT(tbsSz, 0); } - /* Size the output from the key's actual signature length (PQC signatures - * range from a few KB for ML-DSA to tens of KB for large SLH-DSA sets) - * plus headroom for the AlgorithmIdentifier, BIT STRING and SEQUENCE - * wrappers, rather than a fixed magic number. */ -#ifdef WOLFSSL_HAVE_MLDSA - if (mldsaKey != NULL) { - int l = 0; - ExpectIntEQ(wc_MlDsaKey_GetSigLen(mldsaKey, &l), 0); - sigSz = l; - } -#endif -#ifdef WOLFSSL_HAVE_SLHDSA - if (slhDsaKey != NULL) { - sigSz = wc_SlhDsaKey_SigSize(slhDsaKey); - } -#endif - ExpectIntGT(sigSz, 0); + /* wc_SignCRL_ex2 sizes its internal signature buffer from the key; the + * caller's output buffer only needs headroom for the largest signature. + * The biggest supported scheme is SLH-DSA (up to ~50KB), so size generously + * rather than per-algorithm. */ if (EXPECT_SUCCESS()) { - bufSz = tbsSz + sigSz + 512; + bufSz = tbsSz + 64 * 1024; ExpectNotNull(tbsBuf = (byte*)XMALLOC(bufSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); ExpectNotNull(crlBuf = (byte*)XMALLOC(bufSz, NULL, @@ -26319,19 +26302,19 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, ExpectIntGT(tbsSz, 0); } - /* Sign the CRL with the post-quantum key. */ + /* Sign the CRL with the CA key. */ if (EXPECT_SUCCESS()) { crlSz = wc_SignCRL_ex2(tbsBuf, tbsSz, sigType, crlBuf, (word32)bufSz, - keyType, signKey, &rng); + keyType, key, &rng); ExpectIntGT(crlSz, 0); } - /* Negative: a classic signatureAlgorithm OID must be rejected for a PQC + /* Negative: an RSA signatureAlgorithm OID must be rejected for a non-RSA * key before any signature is produced. CheckSigTypeForKey runs before the * TBS is copied into the output, so crlBuf still holds the valid CRL. */ if (EXPECT_SUCCESS()) { ExpectIntEQ(wc_SignCRL_ex2(tbsBuf, tbsSz, CTC_SHA256wRSA, crlBuf, - (word32)bufSz, keyType, signKey, &rng), + (word32)bufSz, keyType, key, &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); } @@ -26370,7 +26353,7 @@ static int pqc_crl_sign_verify(const byte* caCertDer, word32 caCertDerSz, wc_FreeDecodedCert(&caCert); return EXPECT_RESULT(); } -#endif /* CRL gen + (MLDSA | SLHDSA) */ +#endif /* CRL gen + (ED25519 | ED448 | MLDSA | SLHDSA) */ /* Sign and verify CRLs with ML-DSA CA keys for all three parameter sets. */ static int test_wc_SignCRL_mldsa(void) @@ -26427,8 +26410,8 @@ static int test_wc_SignCRL_mldsa(void) &idx), 0); if (EXPECT_SUCCESS()) { - ExpectIntEQ(pqc_crl_sign_verify(certDer, (word32)certDerSz, &key, - NULL, cases[i].keyType, cases[i].sigType), TEST_SUCCESS); + ExpectIntEQ(crl_sign_verify_ex2(certDer, (word32)certDerSz, + cases[i].keyType, &key, cases[i].sigType), TEST_SUCCESS); } if (keyInit) @@ -26487,8 +26470,8 @@ static int test_wc_SignCRL_slhdsa(void) (word32)keyDerSz), 0); if (EXPECT_SUCCESS()) { - ExpectIntEQ(pqc_crl_sign_verify(certDer, (word32)certDerSz, NULL, - &key, cases[i].keyType, cases[i].sigType), TEST_SUCCESS); + ExpectIntEQ(crl_sign_verify_ex2(certDer, (word32)certDerSz, + cases[i].keyType, &key, cases[i].sigType), TEST_SUCCESS); } if (keyInit) @@ -26500,6 +26483,129 @@ static int test_wc_SignCRL_slhdsa(void) return EXPECT_RESULT(); } +/* Sign and verify a CRL with an Ed25519 CA key (wc_SignCRL_ex2). */ +static int test_wc_SignCRL_ed25519(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_ASN) && defined(HAVE_ED25519) && defined(HAVE_ED25519_SIGN) + byte* certDer = NULL; + size_t certDerSz = 0; + byte* keyDer = NULL; + size_t keyDerSz = 0; + ed25519_key key; + int keyInit = 0; + word32 idx = 0; + + ExpectIntEQ(load_file("./certs/ed25519/root-ed25519.der", &certDer, + &certDerSz), 0); + ExpectIntEQ(load_file("./certs/ed25519/root-ed25519-priv.der", &keyDer, + &keyDerSz), 0); + + ExpectIntEQ(wc_ed25519_init(&key), 0); + if (EXPECT_SUCCESS()) + keyInit = 1; + ExpectIntEQ(wc_Ed25519PrivateKeyDecode(keyDer, &idx, &key, + (word32)keyDerSz), 0); + /* The key file carries the private key only, so derive the public key that + * Ed25519 signing needs. */ + ExpectIntEQ(wc_ed25519_make_public(&key, key.p, ED25519_PUB_KEY_SIZE), 0); + if (EXPECT_SUCCESS()) + key.pubKeySet = 1; + + if (EXPECT_SUCCESS()) { + ExpectIntEQ(crl_sign_verify_ex2(certDer, (word32)certDerSz, + ED25519_TYPE, &key, CTC_ED25519), TEST_SUCCESS); + } + + if (keyInit) + wc_ed25519_free(&key); + XFREE(keyDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(certDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* Sign and verify a CRL with an Ed448 CA key (wc_SignCRL_ex2). */ +static int test_wc_SignCRL_ed448(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_ASN) && defined(HAVE_ED448) && defined(HAVE_ED448_SIGN) + byte* certDer = NULL; + size_t certDerSz = 0; + byte* keyDer = NULL; + size_t keyDerSz = 0; + ed448_key key; + int keyInit = 0; + word32 idx = 0; + + ExpectIntEQ(load_file("./certs/ed448/root-ed448.der", &certDer, + &certDerSz), 0); + ExpectIntEQ(load_file("./certs/ed448/root-ed448-priv.der", &keyDer, + &keyDerSz), 0); + + ExpectIntEQ(wc_ed448_init(&key), 0); + if (EXPECT_SUCCESS()) + keyInit = 1; + ExpectIntEQ(wc_Ed448PrivateKeyDecode(keyDer, &idx, &key, + (word32)keyDerSz), 0); + /* The key file carries the private key only, so derive the public key that + * Ed448 signing needs. */ + ExpectIntEQ(wc_ed448_make_public(&key, key.p, ED448_PUB_KEY_SIZE), 0); + if (EXPECT_SUCCESS()) + key.pubKeySet = 1; + + if (EXPECT_SUCCESS()) { + ExpectIntEQ(crl_sign_verify_ex2(certDer, (word32)certDerSz, + ED448_TYPE, &key, CTC_ED448), TEST_SUCCESS); + } + + if (keyInit) + wc_ed448_free(&key); + XFREE(keyDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(certDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* Stateful hash-based schemes (LMS/XMSS) are intentionally rejected for CRL + * signing; wc_SignCRL_ex2 must return ALGO_ID_E for those key types. */ +static int test_wc_SignCRL_stateful_rejected(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_ASN) + WC_RNG rng; + int rngInit = 0; + byte tbs[8]; + byte out[64]; + int dummyKey = 0; + + XMEMSET(tbs, 0, sizeof(tbs)); + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) + rngInit = 1; + + /* The key type is rejected in the dispatch before key is dereferenced, so a + * dummy non-NULL key pointer is sufficient. */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wc_SignCRL_ex2((const byte*)tbs, (int)sizeof(tbs), + CTC_SHA256wRSA, out, (word32)sizeof(out), LMS_TYPE, &dummyKey, + &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); + ExpectIntEQ(wc_SignCRL_ex2((const byte*)tbs, (int)sizeof(tbs), + CTC_SHA256wRSA, out, (word32)sizeof(out), XMSS_TYPE, &dummyKey, + &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); + ExpectIntEQ(wc_SignCRL_ex2((const byte*)tbs, (int)sizeof(tbs), + CTC_SHA256wRSA, out, (word32)sizeof(out), XMSSMT_TYPE, &dummyKey, + &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); + } + + if (rngInit) + wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + static int test_X509_REQ(void) { EXPECT_DECLS; @@ -37492,6 +37598,9 @@ TEST_CASE testCases[] = { TEST_DECL(test_wc_MakeCRL_max_crlnum), TEST_DECL(test_wc_SignCRL_mldsa), TEST_DECL(test_wc_SignCRL_slhdsa), + TEST_DECL(test_wc_SignCRL_ed25519), + TEST_DECL(test_wc_SignCRL_ed448), + TEST_DECL(test_wc_SignCRL_stateful_rejected), /* OpenSSL X509 REQ API test */ TEST_DECL(test_wolfSSL_d2i_X509_REQ), diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 337c506e830..a489cbdefe4 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -37575,19 +37575,18 @@ int wc_MakeCRL_ex(const byte* issuerDer, word32 issuerSz, return (int)idx; } -/* Sign a CRL TBS and produce complete CRL DER. +/* Sign a CRL TBS with an RSA or ECC key and produce the complete CRL DER. * tbsBuf: contains the TBS at the beginning * tbsSz: size of TBS in tbsBuf - * sType: signature type (e.g., CTC_SHA256wRSA, CTC_ML_DSA_44) + * sType: signature type (e.g., CTC_SHA256wRSA, CTC_SHA256wECDSA) * buf: output buffer for complete CRL. May be the same as tbsBuf. * bufSz: size of output buffer - * rsaKey/eccKey/mldsaKey/slhDsaKey: signing key (exactly one must be non-NULL). - * ML-DSA and SLH-DSA produce post-quantum signatures; their (much larger) - * signature buffer is sized from the key rather than assumed classic. The - * PQC key pointers are last so the original RSA/ECC parameter order is - * preserved. + * rsaKey/eccKey: signing key (exactly one must be non-NULL) * rng: random number generator * + * For other key types (Ed25519/Ed448, ML-DSA, SLH-DSA, ...) use + * wc_SignCRL_ex2. + * * Returns: size of complete CRL on success, negative error on failure */ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, @@ -37617,6 +37616,22 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, return wc_SignCRL_ex2(tbsBuf, tbsSz, sType, buf, bufSz, keyType, key, rng); } +/* Sign a CRL TBS with any supported key type and produce the complete CRL DER. + * key is interpreted according to keyType (RSA_TYPE, ECC_TYPE, ED25519_TYPE, + * ED448_TYPE, FALCON_*, ML_DSA_*, SLH_DSA_*), the same selector wc_SignCert_ex + * uses. The signature buffer is sized from the key, so post-quantum signatures + * get enough room. Stateful hash-based schemes (LMS/XMSS) are rejected. + * tbsBuf: contains the TBS at the beginning + * tbsSz: size of TBS in tbsBuf + * sType: signature type matching the key (e.g., CTC_ML_DSA_44) + * buf: output buffer for complete CRL. May be the same as tbsBuf. + * bufSz: size of output buffer + * keyType: selects how key is interpreted + * key: signing key + * rng: random number generator + * + * Returns: size of complete CRL on success, negative error on failure + */ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, byte* buf, word32 bufSz, int keyType, void* key, WC_RNG* rng) @@ -37672,24 +37687,15 @@ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, else if (IsSlhDsaKeyType(keyType)) slhDsaKey = (SlhDsaKey*)key; #endif -#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) - else if (keyType == LMS_TYPE) - lmsKey = (LmsKey*)key; -#endif -#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) - /* The selector must match the key's actual tree variant so XMSS_TYPE and - * XMSSMT_TYPE are not silently interchangeable. */ - else if (keyType == XMSS_TYPE) { - xmssKey = (XmssKey*)key; - if (xmssKey->is_xmssmt) - return BAD_FUNC_ARG; - } - else if (keyType == XMSSMT_TYPE) { - xmssKey = (XmssKey*)key; - if (!xmssKey->is_xmssmt) - return BAD_FUNC_ARG; + else if (keyType == LMS_TYPE || keyType == XMSS_TYPE || + keyType == XMSSMT_TYPE) { + /* Stateful hash-based schemes are intentionally rejected for CRL + * signing: a CRL is reissued periodically and would exhaust the key's + * one-time signature state, with catastrophic reuse risk if that state + * is mismanaged. */ + WOLFSSL_MSG("Stateful signatures (LMS/XMSS) not supported for CRLs"); + return ALGO_ID_E; } -#endif else { return BAD_FUNC_ARG; } From c1a2288aad2cc240d35f5f2e06b8c1d29c06c907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 17 Jul 2026 16:06:17 +0200 Subject: [PATCH 5/6] CRL signing: ungate SLH-DSA dispatch, drop unused LMS/XMSS locals Address review feedback on wc_SignCRL_ex2. Resolve the SLH-DSA key-type dispatch by matching the 12 SLH_DSA_*_TYPE constants directly instead of IsSlhDsaKeyType(), which was only compiled under WOLFSSL_HAVE_SLHDSA and forced that branch to be the only #ifdef'd one. The dispatch is now uniform across all supported key types. Drop the lmsKey and xmssKey locals: LMS/XMSS are rejected before any key pointer is used, so the four signing helpers receive NULL for those slots. --- wolfcrypt/src/asn.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index a489cbdefe4..686abc999d1 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -37649,8 +37649,6 @@ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, falcon_key* falconKey = NULL; wc_MlDsaKey* mldsaKey = NULL; SlhDsaKey* slhDsaKey = NULL; - LmsKey* lmsKey = NULL; - XmssKey* xmssKey = NULL; if (tbsBuf == NULL || tbsSz <= 0 || buf == NULL || key == NULL || rng == NULL) @@ -37683,10 +37681,19 @@ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, mldsaKey = (wc_MlDsaKey*)key; else if (keyType == ML_DSA_87_TYPE) mldsaKey = (wc_MlDsaKey*)key; -#ifdef WOLFSSL_HAVE_SLHDSA - else if (IsSlhDsaKeyType(keyType)) + else if (keyType == SLH_DSA_SHA2_128S_TYPE || + keyType == SLH_DSA_SHA2_128F_TYPE || + keyType == SLH_DSA_SHA2_192S_TYPE || + keyType == SLH_DSA_SHA2_192F_TYPE || + keyType == SLH_DSA_SHA2_256S_TYPE || + keyType == SLH_DSA_SHA2_256F_TYPE || + keyType == SLH_DSA_SHAKE_128S_TYPE || + keyType == SLH_DSA_SHAKE_128F_TYPE || + keyType == SLH_DSA_SHAKE_192S_TYPE || + keyType == SLH_DSA_SHAKE_192F_TYPE || + keyType == SLH_DSA_SHAKE_256S_TYPE || + keyType == SLH_DSA_SHAKE_256F_TYPE) slhDsaKey = (SlhDsaKey*)key; -#endif else if (keyType == LMS_TYPE || keyType == XMSS_TYPE || keyType == XMSSMT_TYPE) { /* Stateful hash-based schemes are intentionally rejected for CRL @@ -37703,7 +37710,7 @@ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, /* The CRL's signatureAlgorithm OID is written from sType while the * signature is produced from the key, so reject a mismatch. */ ret = CheckSigTypeForKey(sType, rsaKey, eccKey, ed25519Key, ed448Key, - falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey); + falconKey, mldsaKey, slhDsaKey, NULL, NULL); if (ret != 0) { WOLFSSL_MSG("Signature type does not match signing key"); return ret; @@ -37712,7 +37719,7 @@ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, XMEMSET(certSignCtx, 0, sizeof(*certSignCtx)); heap = GetSigningKeyHeap(rsaKey, eccKey, ed25519Key, ed448Key, mldsaKey, - slhDsaKey, lmsKey, xmssKey); + slhDsaKey, NULL, NULL); /* Copy TBS to output buffer first */ if ((word32)tbsSz > bufSz) @@ -37723,7 +37730,7 @@ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, * (ML-DSA/SLH-DSA) signatures, which far exceed a classic signature, get * enough room. */ maxSigSz = GetSignatureBufferSz(rsaKey, eccKey, ed25519Key, ed448Key, - falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey); + falconKey, mldsaKey, slhDsaKey, NULL, NULL); if (maxSigSz <= 0) return (maxSigSz < 0) ? maxSigSz : ALGO_ID_E; @@ -37749,8 +37756,8 @@ int wc_SignCRL_ex2(const byte* tbsBuf, int tbsSz, int sType, /* Create signature */ sigSz = MakeSignature(certSignCtx, buf, (word32)tbsSz, certSignCtx->sig, (word32)maxSigSz, rsaKey, eccKey, ed25519Key, - ed448Key, falconKey, mldsaKey, slhDsaKey, lmsKey, - xmssKey, rng, (word32)sType, heap); + ed448Key, falconKey, mldsaKey, slhDsaKey, NULL, + NULL, rng, (word32)sType, heap); if (sigSz < 0) { #ifndef WOLFSSL_NO_MALLOC XFREE(certSignCtx->sig, heap, DYNAMIC_TYPE_TMP_BUFFER); From 4cd9f0874e01bce7aee5705f64d2b6b29e03d472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 17 Jul 2026 16:23:07 +0200 Subject: [PATCH 6/6] tests: fix CRL helper guard to avoid -Wunused-function crl_sign_verify_ex2 was guarded more loosely than its callers, so a build that enables an algorithm but not the extra macro a caller needs (for example WOLFSSL_HAVE_MLDSA without WOLFSSL_PEM_TO_DER) compiled the helper with no caller referencing it, tripping -Wunused-function under -Werror. Guard the helper on the union of the caller test-body guards so it is only compiled when at least one caller references it. --- tests/api.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/api.c b/tests/api.c index 4ac01e2860a..f79e6b079e4 100644 --- a/tests/api.c +++ b/tests/api.c @@ -26221,10 +26221,15 @@ static int test_wc_MakeCRL_max_crlnum(void) return EXPECT_RESULT(); } +/* Guard on the union of the caller test-body guards so this helper is only + * compiled when at least one caller references it (avoids -Wunused-function + * under -Werror). */ #if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ !defined(NO_ASN) && \ - (defined(HAVE_ED25519) || defined(HAVE_ED448) || \ - defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) + ((defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_PEM_TO_DER)) || \ + defined(WOLFSSL_HAVE_SLHDSA) || \ + (defined(HAVE_ED25519) && defined(HAVE_ED25519_SIGN)) || \ + (defined(HAVE_ED448) && defined(HAVE_ED448_SIGN))) /* Build a CRL, sign it with the given CA key through wc_MakeCRL_ex + * wc_SignCRL_ex2, then load it via the certificate manager so the signature is * verified against the issuing CA. keyType selects how key is interpreted.