Skip to content

Commit f5ee4d5

Browse files
authored
Merge pull request #292 from yosuke-wolfssl/fix/f_9852
x509: read the signed certificate DER from the x509 object
2 parents 13e87c1 + 2f63426 commit f5ee4d5

2 files changed

Lines changed: 132 additions & 6 deletions

File tree

‎src/x509/clu_cert_setup.c‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -769,16 +769,20 @@ int wolfCLU_certSetup(int argc, char **argv)
769769

770770
/* write out certificate */
771771
if (ret == WOLFCLU_SUCCESS && !nooutFlag) {
772-
byte *derBuf = inBuf;
773-
byte *pt; /* use pt with i2d to handle potential pointer increment */
772+
const byte *derBuf = inBuf;
774773
int derBufSz = inBufSz;
775774

776775
/* if inform is PEM we convert to DER for excluding input that is not
777776
* part of the certificate */
778777
if (inForm == PEM_FORM) {
779778
if (reqFlag) {
780-
pt = derBuf;
781-
derBufSz = wolfSSL_i2d_X509(x509, &pt);
779+
/* the input buffer is sized for the PEM that was read in,
780+
* not for this certificate's encoding */
781+
derBuf = wolfSSL_X509_get_der(x509, &derBufSz);
782+
if (derBuf == NULL || derBufSz <= 0) {
783+
wolfCLU_LogError("unable to get certificate DER");
784+
ret = WOLFCLU_FATAL_ERROR;
785+
}
782786
}
783787
else {
784788
derBuf = derObj->buffer;
@@ -787,13 +791,13 @@ int wolfCLU_certSetup(int argc, char **argv)
787791
}
788792

789793
/* PEM/DER -> DER */
790-
if (outForm == DER_FORM) {
794+
if (ret == WOLFCLU_SUCCESS && outForm == DER_FORM) {
791795
if (wolfSSL_BIO_write(out, derBuf, derBufSz) <= 0) {
792796
ret = WOLFCLU_FATAL_ERROR;
793797
}
794798
}
795799
/* PEM/DER -> PEM */
796-
else if (outForm == PEM_FORM) {
800+
else if (ret == WOLFCLU_SUCCESS && outForm == PEM_FORM) {
797801
tmpOutBufSz = wc_DerToPem(derBuf, derBufSz, NULL, 0, CERT_TYPE);
798802
if (tmpOutBufSz <= 0) {
799803
wolfCLU_LogError("wc_DerToPem to get necessary length failed");

‎tests/x509/x509-req-test.py‎

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,37 @@ def _tmp(name):
9393
URI.1 = https://www.wolfssl.com
9494
"""
9595

96+
# A CSR built from this config carries no subjectAltName, so the input PEM
97+
# stays short. Applying v3_big_alt with -extfile then grows the signed
98+
# certificate well past the length of that PEM.
99+
BIG_EXT_CONF = """\
100+
[ req ]
101+
distinguished_name =req_distinguished_name
102+
attributes =req_attributes
103+
prompt =no
104+
[ req_distinguished_name ]
105+
countryName =US
106+
commonName = bigext
107+
[ req_attributes ]
108+
[ v3_big_alt ]
109+
basicConstraints = CA:TRUE
110+
keyUsage = digitalSignature
111+
subjectAltName = @big_alt_names
112+
[big_alt_names]
113+
DNS.1 = first-of-the-long-alternative-names.overflow.example.com
114+
DNS.2 = second-of-the-long-alternative-names.overflow.example.com
115+
DNS.3 = third-of-the-long-alternative-names.overflow.example.com
116+
DNS.4 = fourth-of-the-long-alternative-names.overflow.example.com
117+
DNS.5 = fifth-of-the-long-alternative-names.overflow.example.com
118+
DNS.6 = sixth-of-the-long-alternative-names.overflow.example.com
119+
DNS.7 = seventh-of-the-long-alternative-names.overflow.example.com
120+
DNS.8 = eighth-of-the-long-alternative-names.overflow.example.com
121+
DNS.9 = ninth-of-the-long-alternative-names.overflow.example.com
122+
DNS.10 = tenth-of-the-long-alternative-names.overflow.example.com
123+
DNS.11 = eleventh-of-the-long-alternative-names.overflow.example.com
124+
DNS.12 = twelfth-of-the-long-alternative-names.overflow.example.com
125+
"""
126+
96127

97128
def _cleanup(*files):
98129
for f in files:
@@ -751,6 +782,97 @@ def test_extfile_v3_alt_ca(self):
751782
self.assertIn("CA:TRUE", r2.stdout)
752783

753784

785+
class TestX509ReqLargeExtensions(unittest.TestCase):
786+
"""Test x509 -req when the added extensions outgrow the input PEM.
787+
788+
Sizing the output against the input used to write past the buffer
789+
holding it while still emitting correct bytes, so these cases do not
790+
reliably fail on such a build unless a sanitizer catches the write.
791+
The ci.yml matrix runs make check under -fsanitize=address."""
792+
793+
@classmethod
794+
def setUpClass(cls):
795+
cls.conf_file = _tmp("test_x509req_bigext.conf")
796+
with open(cls.conf_file, "w", encoding="utf-8", newline="\n") as f:
797+
f.write(BIG_EXT_CONF)
798+
cls.csr = _tmp("test_x509req_bigext.csr")
799+
r = run_wolfssl("req", "-new",
800+
"-key", os.path.join(CERTS_DIR, "server-key.pem"),
801+
"-config", cls.conf_file,
802+
"-out", cls.csr)
803+
assert r.returncode == 0, "setup CSR creation failed: " + r.stderr
804+
805+
@classmethod
806+
def tearDownClass(cls):
807+
_cleanup(cls.conf_file, cls.csr)
808+
809+
def _clean(self, *files):
810+
for f in files:
811+
self.addCleanup(lambda p=f: _cleanup(p))
812+
813+
def _sign_with_big_ext(self, out, *extra):
814+
"""Sign the CSR applying the oversized v3_big_alt extensions."""
815+
r = run_wolfssl("x509", "-req", "-in", self.csr, "-days", "3650",
816+
"-extfile", self.conf_file,
817+
"-extensions", "v3_big_alt",
818+
"-signkey",
819+
os.path.join(CERTS_DIR, "server-key.pem"),
820+
"-out", out, *extra)
821+
combined = r.stdout + r.stderr
822+
if "not compiled with cert extensions" in combined:
823+
self.skipTest("cert extensions not compiled in")
824+
if "WOLFSSL_ALT_NAMES" in combined:
825+
self.skipTest("alt names not compiled in")
826+
self.assertEqual(r.returncode, 0, r.stderr)
827+
828+
def _assert_alt_names(self, text):
829+
for name in ("first-of-the-long-alternative-names.overflow.example.com",
830+
"sixth-of-the-long-alternative-names.overflow.example.com",
831+
"twelfth-of-the-long-alternative-names.overflow."
832+
"example.com"):
833+
self.assertIn(name, text)
834+
835+
def test_extfile_larger_than_input_pem_der_out(self):
836+
"""x509 -req -outform der output may exceed the input PEM length.
837+
838+
The certificate is re-encoded after the extensions are applied, so
839+
its DER outgrows the CSR PEM that was read in. Nothing on the
840+
output path may be sized against that input."""
841+
out = _tmp("tmp_bigext_der.cert")
842+
self._clean(out)
843+
844+
csr_pem_len = os.path.getsize(self.csr)
845+
self._sign_with_big_ext(out, "-outform", "der")
846+
847+
# Without the extensions the output cannot outgrow the input, and
848+
# this case would no longer cover the sizing it is here to check.
849+
self.assertGreater(os.path.getsize(out), csr_pem_len,
850+
"certificate DER did not outgrow the input PEM")
851+
852+
r2 = run_wolfssl("x509", "-in", out, "-inform", "der",
853+
"-text", "-noout")
854+
self.assertEqual(r2.returncode, 0, r2.stderr)
855+
self._assert_alt_names(r2.stdout)
856+
857+
def test_extfile_larger_than_input_pem_pem_out(self):
858+
"""x509 -req PEM output covers the same sizing on the DerToPem path."""
859+
out = _tmp("tmp_bigext_pem.cert")
860+
self._clean(out)
861+
862+
csr_pem_len = os.path.getsize(self.csr)
863+
self._sign_with_big_ext(out)
864+
865+
with open(out, encoding="utf-8") as f:
866+
pem = f.read()
867+
self.assertTrue(pem.startswith("-----BEGIN CERTIFICATE-----"),
868+
"default output form should be PEM")
869+
self.assertGreater(os.path.getsize(out), csr_pem_len,
870+
"certificate PEM did not outgrow the input PEM")
871+
872+
r2 = run_wolfssl("x509", "-in", out, "-text", "-noout")
873+
self.assertEqual(r2.returncode, 0, r2.stderr)
874+
self._assert_alt_names(r2.stdout)
875+
754876

755877
class TestReqConfigSubject(unittest.TestCase):
756878
"""Test subject from config file."""

0 commit comments

Comments
 (0)