From 837583fb7acaf39625b1e06f08ce4e46665c6b84 Mon Sep 17 00:00:00 2001 From: Kareem Date: Mon, 9 Nov 2026 17:47:41 -0700 Subject: [PATCH] Fix handling of multiple certs in a single DER in wolfSSL_d2i_X509. The trailing data rejection broke this function. Trailing data check should be disabled for it as it processes one cert at a time and advances the input pointer. If there is trailing junk, the next call will catch it. --- src/x509.c | 13 ++++--- tests/api/test_ossl_x509_io.c | 64 +++++++++++++++++++++++++++++++++++ tests/api/test_ossl_x509_io.h | 3 ++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/x509.c b/src/x509.c index a40a2d97152..627191ae1c0 100644 --- a/src/x509.c +++ b/src/x509.c @@ -4582,6 +4582,9 @@ char* wolfSSL_X509_get_name_oneline(WOLFSSL_X509_NAME* name, char* in, int sz) #endif +static WOLFSSL_X509* d2i_X509orX509REQ(WOLFSSL_X509** x509, + const byte* in, int len, int req, byte allowTrailing, void* heap); + /* Wraps wolfSSL_X509_d2i * * returns a WOLFSSL_X509 structure pointer on success and NULL on fail @@ -4597,7 +4600,8 @@ WOLFSSL_X509* wolfSSL_d2i_X509(WOLFSSL_X509** x509, const unsigned char** in, return NULL; } - newX509 = wolfSSL_X509_d2i(x509, *in, len); + /* len only bounds the input; *in is advanced past this certificate. */ + newX509 = d2i_X509orX509REQ(x509, *in, len, 0, 1, NULL); if (newX509 != NULL) { *in += newX509->derCert->length; } @@ -4605,7 +4609,7 @@ WOLFSSL_X509* wolfSSL_d2i_X509(WOLFSSL_X509** x509, const unsigned char** in, } static WOLFSSL_X509* d2i_X509orX509REQ(WOLFSSL_X509** x509, - const byte* in, int len, int req, void* heap) + const byte* in, int len, int req, byte allowTrailing, void* heap) { WOLFSSL_X509 *newX509 = NULL; int type = req ? CERTREQ_TYPE : CERT_TYPE; @@ -4628,6 +4632,7 @@ static WOLFSSL_X509* d2i_X509orX509REQ(WOLFSSL_X509** x509, #ifdef WOLFSSL_CERT_REQ cert->isCSR = (byte)req; #endif + cert->allowTrailing = allowTrailing; #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) /* CopyDecodedToX509() adds the name entries to the X509 directly. */ cert->noX509Name = 1; @@ -4668,7 +4673,7 @@ int wolfSSL_X509_get_isCA(WOLFSSL_X509* x509) WOLFSSL_X509* wolfSSL_X509_d2i_ex(WOLFSSL_X509** x509, const byte* in, int len, void* heap) { - return d2i_X509orX509REQ(x509, in, len, 0, heap); + return d2i_X509orX509REQ(x509, in, len, 0, 0, heap); } WOLFSSL_X509* wolfSSL_X509_d2i(WOLFSSL_X509** x509, const byte* in, int len) @@ -4680,7 +4685,7 @@ WOLFSSL_X509* wolfSSL_X509_d2i(WOLFSSL_X509** x509, const byte* in, int len) WOLFSSL_X509* wolfSSL_X509_REQ_d2i(WOLFSSL_X509** x509, const unsigned char* in, int len) { - return d2i_X509orX509REQ(x509, in, len, 1, NULL); + return d2i_X509orX509REQ(x509, in, len, 1, 0, NULL); } WOLFSSL_X509* wolfSSL_d2i_X509_REQ_INFO(WOLFSSL_X509** req, diff --git a/tests/api/test_ossl_x509_io.c b/tests/api/test_ossl_x509_io.c index 8e3cd7673e9..fc6bdb66f02 100644 --- a/tests/api/test_ossl_x509_io.c +++ b/tests/api/test_ossl_x509_io.c @@ -72,6 +72,70 @@ int test_wolfSSL_i2d_X509(void) return EXPECT_RESULT(); } +int test_wolfSSL_d2i_X509_concatenated(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(USE_CERT_BUFFERS_2048) && !defined(NO_RSA) + const word32 srvSz = (word32)sizeof_server_cert_der_2048; + const word32 caSz = (word32)sizeof_ca_cert_der_2048; + const word32 junkSz = 8; + byte* buf = NULL; + const unsigned char* p = NULL; + const byte* der = NULL; + int derSz = 0; + X509* cert = NULL; + + ExpectNotNull(buf = (byte*)XMALLOC(srvSz + caSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (buf != NULL) { + XMEMCPY(buf, server_cert_der_2048, srvSz); + XMEMCPY(buf + srvSz, ca_cert_der_2048, caSz); + } + + /* Each call decodes one certificate and advances past it only. */ + p = buf; + ExpectNotNull(cert = d2i_X509(NULL, &p, (long)(srvSz + caSz))); + ExpectPtrEq(p, buf + srvSz); + ExpectNotNull(der = wolfSSL_X509_get_der(cert, &derSz)); + ExpectIntEQ(derSz, (int)srvSz); + ExpectBufEQ(der, server_cert_der_2048, srvSz); + X509_free(cert); + cert = NULL; + ExpectNotNull(cert = d2i_X509(NULL, &p, (long)caSz)); + ExpectPtrEq(p, buf + srvSz + caSz); + ExpectNotNull(der = wolfSSL_X509_get_der(cert, &derSz)); + ExpectIntEQ(derSz, (int)caSz); + ExpectBufEQ(der, ca_cert_der_2048, caSz); + X509_free(cert); + cert = NULL; + + /* A truncated certificate is rejected and the pointer is untouched. */ + p = buf; + ExpectNull(d2i_X509(NULL, &p, (long)(srvSz - 1))); + ExpectPtrEq(p, buf); + + /* Trailing junk is left for the caller and fails to decode on its own. */ + if (buf != NULL) { + XMEMSET(buf + srvSz, 0xA5, junkSz); + } + p = buf; + ExpectNotNull(cert = d2i_X509(NULL, &p, (long)(srvSz + junkSz))); + ExpectPtrEq(p, buf + srvSz); + X509_free(cert); + cert = NULL; + ExpectNull(d2i_X509(NULL, &p, (long)junkSz)); + ExpectPtrEq(p, buf + srvSz); + + /* wolfSSL_X509_d2i() takes an exact length and still rejects junk. */ +#ifndef WOLFSSL_NO_ASN_STRICT + ExpectNull(wolfSSL_X509_d2i(NULL, buf, (int)(srvSz + junkSz))); +#endif + + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_X509_get_der_length_guards(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_x509_io.h b/tests/api/test_ossl_x509_io.h index f8ef4d4a513..b82fb5a2391 100644 --- a/tests/api/test_ossl_x509_io.h +++ b/tests/api/test_ossl_x509_io.h @@ -25,6 +25,7 @@ #include int test_wolfSSL_i2d_X509(void); +int test_wolfSSL_d2i_X509_concatenated(void); int test_wolfSSL_X509_get_der_length_guards(void); int test_wolfSSL_i2d_X509_der_length_guards(void); int test_wolfSSL_PEM_read_X509(void); @@ -32,6 +33,8 @@ int test_wolfSSL_PEM_write_bio_X509(void); #define TEST_OSSL_X509_IO_DECLS \ TEST_DECL_GROUP("ossl_x509_io", test_wolfSSL_i2d_X509), \ + TEST_DECL_GROUP("ossl_x509_io", \ + test_wolfSSL_d2i_X509_concatenated), \ TEST_DECL_GROUP("ossl_x509_io", \ test_wolfSSL_X509_get_der_length_guards), \ TEST_DECL_GROUP("ossl_x509_io", \