From 2f6342652d119702963145db7ab2e340b91d17c0 Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Fri, 4 Sep 2026 14:47:53 +0900 Subject: [PATCH] x509: read the signed certificate DER from the x509 object - wolfCLU_certSetup() takes the re-encoded certificate from wolfSSL_X509_get_der() instead of encoding it into inBuf with wolfSSL_i2d_X509(), and errors out when that returns NULL or a non-positive length. - derBuf is a const byte pointer, and the pt local that carried the i2d pointer increment is removed. - The DER and PEM output branches run only while ret is WOLFCLU_SUCCESS. - tests/x509/x509-req-test.py adds TestX509ReqLargeExtensions, which signs a CSR with a twelve entry subjectAltName section supplied by -extfile and checks that both the DER and the PEM output form are longer than the input PEM and carry the names, skipping when the build has no cert extensions or alt names. Issue: F-9852 --- src/x509/clu_cert_setup.c | 16 +++-- tests/x509/x509-req-test.py | 122 ++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 6 deletions(-) diff --git a/src/x509/clu_cert_setup.c b/src/x509/clu_cert_setup.c index a7cda803..1f05998b 100644 --- a/src/x509/clu_cert_setup.c +++ b/src/x509/clu_cert_setup.c @@ -769,16 +769,20 @@ int wolfCLU_certSetup(int argc, char **argv) /* write out certificate */ if (ret == WOLFCLU_SUCCESS && !nooutFlag) { - byte *derBuf = inBuf; - byte *pt; /* use pt with i2d to handle potential pointer increment */ + const byte *derBuf = inBuf; int derBufSz = inBufSz; /* if inform is PEM we convert to DER for excluding input that is not * part of the certificate */ if (inForm == PEM_FORM) { if (reqFlag) { - pt = derBuf; - derBufSz = wolfSSL_i2d_X509(x509, &pt); + /* the input buffer is sized for the PEM that was read in, + * not for this certificate's encoding */ + derBuf = wolfSSL_X509_get_der(x509, &derBufSz); + if (derBuf == NULL || derBufSz <= 0) { + wolfCLU_LogError("unable to get certificate DER"); + ret = WOLFCLU_FATAL_ERROR; + } } else { derBuf = derObj->buffer; @@ -787,13 +791,13 @@ int wolfCLU_certSetup(int argc, char **argv) } /* PEM/DER -> DER */ - if (outForm == DER_FORM) { + if (ret == WOLFCLU_SUCCESS && outForm == DER_FORM) { if (wolfSSL_BIO_write(out, derBuf, derBufSz) <= 0) { ret = WOLFCLU_FATAL_ERROR; } } /* PEM/DER -> PEM */ - else if (outForm == PEM_FORM) { + else if (ret == WOLFCLU_SUCCESS && outForm == PEM_FORM) { tmpOutBufSz = wc_DerToPem(derBuf, derBufSz, NULL, 0, CERT_TYPE); if (tmpOutBufSz <= 0) { wolfCLU_LogError("wc_DerToPem to get necessary length failed"); diff --git a/tests/x509/x509-req-test.py b/tests/x509/x509-req-test.py index 772a7bef..82b1153e 100644 --- a/tests/x509/x509-req-test.py +++ b/tests/x509/x509-req-test.py @@ -93,6 +93,37 @@ def _tmp(name): URI.1 = https://www.wolfssl.com """ +# A CSR built from this config carries no subjectAltName, so the input PEM +# stays short. Applying v3_big_alt with -extfile then grows the signed +# certificate well past the length of that PEM. +BIG_EXT_CONF = """\ +[ req ] +distinguished_name =req_distinguished_name +attributes =req_attributes +prompt =no +[ req_distinguished_name ] +countryName =US +commonName = bigext +[ req_attributes ] +[ v3_big_alt ] +basicConstraints = CA:TRUE +keyUsage = digitalSignature +subjectAltName = @big_alt_names +[big_alt_names] +DNS.1 = first-of-the-long-alternative-names.overflow.example.com +DNS.2 = second-of-the-long-alternative-names.overflow.example.com +DNS.3 = third-of-the-long-alternative-names.overflow.example.com +DNS.4 = fourth-of-the-long-alternative-names.overflow.example.com +DNS.5 = fifth-of-the-long-alternative-names.overflow.example.com +DNS.6 = sixth-of-the-long-alternative-names.overflow.example.com +DNS.7 = seventh-of-the-long-alternative-names.overflow.example.com +DNS.8 = eighth-of-the-long-alternative-names.overflow.example.com +DNS.9 = ninth-of-the-long-alternative-names.overflow.example.com +DNS.10 = tenth-of-the-long-alternative-names.overflow.example.com +DNS.11 = eleventh-of-the-long-alternative-names.overflow.example.com +DNS.12 = twelfth-of-the-long-alternative-names.overflow.example.com +""" + def _cleanup(*files): for f in files: @@ -751,6 +782,97 @@ def test_extfile_v3_alt_ca(self): self.assertIn("CA:TRUE", r2.stdout) +class TestX509ReqLargeExtensions(unittest.TestCase): + """Test x509 -req when the added extensions outgrow the input PEM. + + Sizing the output against the input used to write past the buffer + holding it while still emitting correct bytes, so these cases do not + reliably fail on such a build unless a sanitizer catches the write. + The ci.yml matrix runs make check under -fsanitize=address.""" + + @classmethod + def setUpClass(cls): + cls.conf_file = _tmp("test_x509req_bigext.conf") + with open(cls.conf_file, "w", encoding="utf-8", newline="\n") as f: + f.write(BIG_EXT_CONF) + cls.csr = _tmp("test_x509req_bigext.csr") + r = run_wolfssl("req", "-new", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-config", cls.conf_file, + "-out", cls.csr) + assert r.returncode == 0, "setup CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf_file, cls.csr) + + def _clean(self, *files): + for f in files: + self.addCleanup(lambda p=f: _cleanup(p)) + + def _sign_with_big_ext(self, out, *extra): + """Sign the CSR applying the oversized v3_big_alt extensions.""" + r = run_wolfssl("x509", "-req", "-in", self.csr, "-days", "3650", + "-extfile", self.conf_file, + "-extensions", "v3_big_alt", + "-signkey", + os.path.join(CERTS_DIR, "server-key.pem"), + "-out", out, *extra) + combined = r.stdout + r.stderr + if "not compiled with cert extensions" in combined: + self.skipTest("cert extensions not compiled in") + if "WOLFSSL_ALT_NAMES" in combined: + self.skipTest("alt names not compiled in") + self.assertEqual(r.returncode, 0, r.stderr) + + def _assert_alt_names(self, text): + for name in ("first-of-the-long-alternative-names.overflow.example.com", + "sixth-of-the-long-alternative-names.overflow.example.com", + "twelfth-of-the-long-alternative-names.overflow." + "example.com"): + self.assertIn(name, text) + + def test_extfile_larger_than_input_pem_der_out(self): + """x509 -req -outform der output may exceed the input PEM length. + + The certificate is re-encoded after the extensions are applied, so + its DER outgrows the CSR PEM that was read in. Nothing on the + output path may be sized against that input.""" + out = _tmp("tmp_bigext_der.cert") + self._clean(out) + + csr_pem_len = os.path.getsize(self.csr) + self._sign_with_big_ext(out, "-outform", "der") + + # Without the extensions the output cannot outgrow the input, and + # this case would no longer cover the sizing it is here to check. + self.assertGreater(os.path.getsize(out), csr_pem_len, + "certificate DER did not outgrow the input PEM") + + r2 = run_wolfssl("x509", "-in", out, "-inform", "der", + "-text", "-noout") + self.assertEqual(r2.returncode, 0, r2.stderr) + self._assert_alt_names(r2.stdout) + + def test_extfile_larger_than_input_pem_pem_out(self): + """x509 -req PEM output covers the same sizing on the DerToPem path.""" + out = _tmp("tmp_bigext_pem.cert") + self._clean(out) + + csr_pem_len = os.path.getsize(self.csr) + self._sign_with_big_ext(out) + + with open(out, encoding="utf-8") as f: + pem = f.read() + self.assertTrue(pem.startswith("-----BEGIN CERTIFICATE-----"), + "default output form should be PEM") + self.assertGreater(os.path.getsize(out), csr_pem_len, + "certificate PEM did not outgrow the input PEM") + + r2 = run_wolfssl("x509", "-in", out, "-text", "-noout") + self.assertEqual(r2.returncode, 0, r2.stderr) + self._assert_alt_names(r2.stdout) + class TestReqConfigSubject(unittest.TestCase): """Test subject from config file."""