From 8f1d3404103d81240a775af54f74bb69763eee2b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 21:26:32 +0000 Subject: [PATCH 1/2] 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 ed32de3f36a..e48961b6874 100644 --- a/src/crl.c +++ b/src/crl.c @@ -2903,7 +2903,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 e81fee2b5df..79d21e70144 100644 --- a/tests/api.c +++ b/tests/api.c @@ -172,6 +172,9 @@ #ifdef WOLFSSL_HAVE_MLDSA #include #endif +#ifdef WOLFSSL_HAVE_SLHDSA + #include +#endif #if defined(WOLFSSL_HAVE_MLKEM) #include #endif @@ -24335,7 +24338,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); } @@ -24344,7 +24347,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)); } @@ -24409,6 +24412,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; @@ -34872,6 +35120,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 a1b840b0235..76864c5fd2f 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -36915,36 +36915,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; @@ -36952,17 +36960,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; @@ -36970,15 +36985,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 1c6bfc38539..9b0756b187a 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 7a56ec3773461f54704b429d4416833533dc56c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 23:04:16 +0000 Subject: [PATCH 2/2] 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 e48961b6874..9c92086043d 100644 --- a/src/crl.c +++ b/src/crl.c @@ -2903,7 +2903,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 79d21e70144..35e681db5b7 100644 --- a/tests/api.c +++ b/tests/api.c @@ -24338,7 +24338,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); } @@ -24347,7 +24347,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)); } @@ -24430,13 +24430,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. */ @@ -24451,15 +24452,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); @@ -24473,10 +24475,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, @@ -24493,10 +24510,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, @@ -24506,7 +24532,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; @@ -24515,12 +24543,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 76864c5fd2f..787799efdf1 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -36920,15 +36920,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; @@ -36942,10 +36944,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 9b0756b187a..f2c8470539c 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,