src/x509.c: refactor wolfSSL_PEM_read_bio_X509_CRL onto the per-block reader

ReadPemFromBioToBuffer slurps the entire BIO in one shot, so iterative
callers like wolfSSL_PEM_read_bio_X509_CRL (and by extension
wolfSSL_X509_load_crl_file's BIO branch) saw EOF after the first block
and silently dropped every CRL after the first in a multi-CRL bundle.

Refactor wolfSSL_PEM_read_bio_X509_CRL to delegate to
wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio, which already reads one
PEM BEGIN/END pair per call and leaves the BIO positioned just past the
END line. Loop over it so we skip past intervening cert/key blocks and
return the next CRL in the stream — matching OpenSSL's
PEM_read_bio_X509_CRL, verified against OpenSSL 3.0.13 with cases
{cert,CRL}, {CRL,cert}, {CRL,cert,CRL}, {key,CRL}, {CRL,key,CRL}: in
each case OpenSSL skips non-CRL blocks until EOF.

When the caller passes a non-NULL `x` whose `*x` is already populated,
free the previous CRL before overwriting the slot — matching the
d2i_X509_CRL reuse contract the old body relied on.

To keep both helpers visible at the new call site, drop their `static`
qualifier (wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio for the per-block
read, wolfSSL_X509_PKEY_free to free defensively-allocated keys parsed
from intervening non-CRL blocks). Their definitions in src/x509.c and
declarations in wolfssl/internal.h are widened from OPENSSL_ALL to
OPENSSL_EXTRA || OPENSSL_ALL so the OPENSSL_EXTRA-only build (which
compiles wolfSSL_PEM_read_bio_X509_CRL) links cleanly. The unrelated
INFO_read_bio / INFO_read_bio_X509_INFO group below them keeps its
OPENSSL_ALL gate because it depends on wolfSSL_X509_INFO_new/free that
are still OPENSSL_ALL-only.

Also register the previously-orphaned test_wolfSSL_X509_load_crl_file
(its slot in TEST_OSSL_X509_LOOKUP_DECLS was a duplicated
test_wolfSSL_X509_LOOKUP_ctrl_hash_dir entry), update its assertion for
crl2.pem (which already contains two CRLs) to expect 2 instead of 1, and
add a multi-CRL bundle case that builds a memory BIO from
crl.pem + server-cert.pem + crl2.pem and asserts that the reader walks
past the cert and returns all 3 CRLs before NULL.
This commit is contained in:
Juliusz Sosinowicz
2026-04-28 10:06:47 +00:00
parent 1c9555c121
commit 4a85f00240
4 changed files with 89 additions and 28 deletions
+28 -26
View File
@@ -13499,36 +13499,35 @@ WOLFSSL_X509 *wolfSSL_PEM_read_bio_X509_REQ(WOLFSSL_BIO *bp, WOLFSSL_X509 **x,
WOLFSSL_X509_CRL **x, wc_pem_password_cb *cb, void *u)
{
#if defined(WOLFSSL_PEM_TO_DER) && defined(HAVE_CRL)
unsigned char* pem = NULL;
int pemSz = 0;
int derSz = 0;
DerBuffer* der = NULL;
WOLFSSL_X509_CRL* crl = NULL;
WOLFSSL_ENTER("wolfSSL_PEM_read_bio_X509_CRL");
if ((pem = ReadPemFromBioToBuffer(bp, &pemSz)) == NULL) {
goto err;
/* OpenSSL's PEM_read_bio_X509_CRL skips intervening cert/key blocks
* and returns the next CRL in the stream (NULL only at EOF). Mirror
* that by looping over the per-block reader until we get a CRL or
* the BIO has nothing left to parse. */
for (;;) {
WOLFSSL_X509* x509 = NULL;
WOLFSSL_X509_PKEY* x_pkey = NULL;
if (wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio(bp, cb,
&x509, &crl, &x_pkey) != WOLFSSL_SUCCESS) {
break;
}
if (crl != NULL) {
break;
}
wolfSSL_X509_free(x509);
wolfSSL_X509_PKEY_free(x_pkey);
}
if ((PemToDer(pem, pemSz, CRL_TYPE, &der, NULL, NULL, NULL)) < 0) {
goto err;
}
derSz = (int)der->length;
if ((crl = wolfSSL_d2i_X509_CRL(x, der->buffer, derSz)) == NULL) {
goto err;
if (x != NULL) {
if (*x != NULL && *x != crl) {
wolfSSL_X509_CRL_free(*x);
}
*x = crl;
}
err:
if (pemSz == 0) {
WOLFSSL_ERROR(ASN_NO_PEM_HEADER);
}
XFREE(pem, 0, DYNAMIC_TYPE_PEM);
if (der != NULL) {
FreeDer(&der);
}
(void)cb;
(void)u;
return crl;
@@ -13691,7 +13690,7 @@ int wolfSSL_write_X509_CRL(WOLFSSL_X509_CRL* crl, const char* path, int type)
#endif /* !NO_FILESYSTEM */
#endif /* OPENSSL_EXTRA || OPENSSL_ALL */
#ifdef OPENSSL_ALL
#if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)
#ifndef NO_BIO
/* create and return a new WOLFSSL_X509_PKEY structure or NULL on failure */
@@ -13711,7 +13710,7 @@ int wolfSSL_write_X509_CRL(WOLFSSL_X509_CRL* crl, const char* path, int type)
/* free up all memory used by "xPkey" passed in */
static void wolfSSL_X509_PKEY_free(WOLFSSL_X509_PKEY* xPkey)
void wolfSSL_X509_PKEY_free(WOLFSSL_X509_PKEY* xPkey)
{
if (xPkey != NULL) {
wolfSSL_EVP_PKEY_free(xPkey->dec_pkey);
@@ -13737,7 +13736,7 @@ int wolfSSL_write_X509_CRL(WOLFSSL_X509_CRL* crl, const char* path, int type)
* @param x_pkey Output
* @return WOLFSSL_SUCCESS on success and WOLFSSL_FAILURE otherwise
*/
static int wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio(
int wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio(
WOLFSSL_BIO* bio, wc_pem_password_cb* cb, WOLFSSL_X509** x509,
WOLFSSL_X509_CRL** crl, WOLFSSL_X509_PKEY** x_pkey)
{
@@ -13921,6 +13920,9 @@ err:
#endif /* WOLFSSL_PEM_TO_DER || WOLFSSL_DER_TO_PEM */
}
#endif /* OPENSSL_EXTRA || OPENSSL_ALL */
#ifdef OPENSSL_ALL
#ifndef NO_FILESYSTEM
WOLF_STACK_OF(WOLFSSL_X509_INFO)* wolfSSL_PEM_X509_INFO_read(
XFILE fp, WOLF_STACK_OF(WOLFSSL_X509_INFO)* sk,
@@ -14058,7 +14060,7 @@ err:
return localSk;
}
#endif /* !NO_BIO */
#endif /* OPENSSL_ALL */
#endif /* OPENSSL_EXTRA || OPENSSL_ALL */
void wolfSSL_X509_NAME_ENTRY_free(WOLFSSL_X509_NAME_ENTRY* ne)
{
+51 -1
View File
@@ -312,6 +312,17 @@ int test_wolfSSL_X509_load_crl_file(void)
#endif
""
};
int pemCount[] = {
1,
2,
1,
1,
1,
#ifdef WC_RSA_PSS
1,
#endif
0
};
char der[][100] = {
"./certs/crl/crl.der",
"./certs/crl/crl2.der",
@@ -342,7 +353,7 @@ int test_wolfSSL_X509_load_crl_file(void)
ExpectIntEQ(X509_load_crl_file(lookup, pem[0], 0), 0);
for (i = 0; pem[i][0] != '\0'; i++) {
ExpectIntEQ(X509_load_crl_file(lookup, pem[i], WOLFSSL_FILETYPE_PEM),
1);
pemCount[i]);
}
if (store) {
@@ -394,6 +405,45 @@ int test_wolfSSL_X509_load_crl_file(void)
X509_STORE_free(store);
store = NULL;
/* Combine crl.pem (1 CRL), a server cert, and crl2.pem (2 CRLs) into a
* single memory BIO. wolfSSL_PEM_read_bio_X509_CRL must walk past the
* intervening certificate and hand back all three CRLs before NULL,
* matching OpenSSL's PEM_read_bio_X509_CRL behaviour. */
{
WOLFSSL_BIO* fileBio = NULL;
WOLFSSL_BIO* memBio = NULL;
WOLFSSL_X509_CRL* crl = NULL;
unsigned char buf[4096];
int n;
int crlCount = 0;
const char* sources[] = {
"./certs/crl/crl.pem",
"./certs/server-cert.pem",
"./certs/crl/crl2.pem",
NULL
};
ExpectNotNull(memBio = wolfSSL_BIO_new(wolfSSL_BIO_s_mem()));
for (i = 0; sources[i] != NULL; i++) {
ExpectNotNull(fileBio = wolfSSL_BIO_new_file(sources[i], "rb"));
while (fileBio != NULL &&
(n = wolfSSL_BIO_read(fileBio, buf, sizeof(buf))) > 0) {
ExpectIntEQ(wolfSSL_BIO_write(memBio, buf, n), n);
}
wolfSSL_BIO_free(fileBio);
fileBio = NULL;
}
while ((crl = wolfSSL_PEM_read_bio_X509_CRL(memBio, NULL, NULL, NULL))
!= NULL) {
crlCount++;
wolfSSL_X509_CRL_free(crl);
}
ExpectIntEQ(crlCount, 3);
wolfSSL_BIO_free(memBio);
}
#endif
return EXPECT_RESULT();
}
+1 -1
View File
@@ -34,7 +34,7 @@ int test_X509_LOOKUP_add_dir(void);
TEST_DECL_GROUP("ossl_x509_lu", test_wolfSSL_X509_LOOKUP_load_file), \
TEST_DECL_GROUP("ossl_x509_lu", test_wolfSSL_X509_LOOKUP_ctrl_file), \
TEST_DECL_GROUP("ossl_x509_lu", test_wolfSSL_X509_LOOKUP_ctrl_hash_dir), \
TEST_DECL_GROUP("ossl_x509_lu", test_wolfSSL_X509_LOOKUP_ctrl_hash_dir), \
TEST_DECL_GROUP("ossl_x509_lu", test_wolfSSL_X509_load_crl_file), \
TEST_DECL_GROUP("ossl_x509_lu", test_X509_LOOKUP_add_dir)
#endif /* WOLFCRYPT_TEST_OSSL_X509_LU_H */
+9
View File
@@ -7469,6 +7469,15 @@ WOLFSSL_LOCAL int pkcs8_encrypt(WOLFSSL_EVP_PKEY* pkey,
word32* keySz);
#endif /* OPENSSL_EXTRA || OPENSSL_EXTRA_X509_SMALL */
#if (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && !defined(NO_BIO)
WOLFSSL_LOCAL int wolfSSL_PEM_X509_X509_CRL_X509_PKEY_read_bio(
WOLFSSL_BIO* bio, wc_pem_password_cb* cb, WOLFSSL_X509** x509,
WOLFSSL_X509_CRL** crl, WOLFSSL_X509_PKEY** x_pkey);
#endif
#if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)
WOLFSSL_LOCAL void wolfSSL_X509_PKEY_free(WOLFSSL_X509_PKEY* xPkey);
#endif
WOLFSSL_LOCAL void wolfssl_local_MaybeCheckAlertOnErr(WOLFSSL* ssl, int err);
#ifdef __cplusplus