From f429097cddbf2393b9fbb51a1db6c328566e0ffb Mon Sep 17 00:00:00 2001 From: v1rtu3x <149050962+v1rtu3x@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:53:40 +0300 Subject: [PATCH] x509: Accept basicConstraints with omitted cA RFC 5280 defines cA as BOOLEAN DEFAULT FALSE, so a basicConstraints extension containing only pathLenConstraint (SEQUENCE { INTEGER n }) is syntactically valid and must be interpreted as cA=FALSE. The fix for CVE-2026-49300 rejected this encoding outright, breaking compatibility with certificates emitted by devices such as Eaton/CODESYS PLCs. Tolerate MBEDTLS_ERR_ASN1_UNEXPECTED_TAG from the cA BOOLEAN parse, keeping cA FALSE and letting the existing INTEGER parser validate pathLenConstraint. The INTEGER value is never used to determine cA, so the CVE fix is preserved. Fixes #11006 Signed-off-by: v1rtu3x <149050962+v1rtu3x@users.noreply.github.com> (cherry picked from commit 62dd8a556c01cc289d4fb3f19352a78ede90dac2) --- .../x509-basic-constraints-integer-first.txt | 5 +++++ library/x509_crt.c | 12 +++++----- tests/suites/test_suite_x509parse.data | 20 +++++++++++++++-- tests/suites/test_suite_x509parse.function | 22 +++++++++++++++++++ 4 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 ChangeLog.d/x509-basic-constraints-integer-first.txt diff --git a/ChangeLog.d/x509-basic-constraints-integer-first.txt b/ChangeLog.d/x509-basic-constraints-integer-first.txt new file mode 100644 index 0000000000..409a626aa0 --- /dev/null +++ b/ChangeLog.d/x509-basic-constraints-integer-first.txt @@ -0,0 +1,5 @@ +Bugfix + * Accept a basicConstraints extension that omits cA and contains only + pathLenConstraint, interpreting it as cA=FALSE as specified by RFC 5280. + This restores compatibility with certificates emitted by some devices + without accepting them as CA certificates. Fixes #11006. diff --git a/library/x509_crt.c b/library/x509_crt.c index a6fcd1b4e6..e3549a1c2b 100644 --- a/library/x509_crt.c +++ b/library/x509_crt.c @@ -518,17 +518,19 @@ static int x509_get_basic_constraints(unsigned char **p, } if ((ret = mbedtls_asn1_get_bool(p, end, ca_istrue)) != 0) { - /* If the SEQUENCE starts with an INTEGER and not a BOOLEAN, - * according to RFC 5280, it's syntactically valid, but it's - * something that a CA MUST NOT produce. So we reject it. + /* cA is DEFAULT FALSE. If it is absent, the first element must + * be pathLenConstraint. Keep cA false and let the INTEGER parser + * below validate the element. * * Note: historically, from XySSL 0.9 to Mbed TLS 3.6.6/4.1.0, * `SEQUENCE { INTEGER n }` was interpreted as cA=FALSE if n == 0 * and cA=TRUE if n != 0. This was dangerous since * `SEQUENCE { INTEGER n }` should be parsed as cA=FALSE - * according to RFC 5280, so we stopped doing it. + * according to RFC 5280. */ - return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + if (ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } } /* `SEQUENCE { BOOLEAN FALSE }` is not DER since default-value fields * must be omitted in DER. But it seems harmless, and Mbed TLS has diff --git a/tests/suites/test_suite_x509parse.data b/tests/suites/test_suite_x509parse.data index b0cb873f2b..19ddc79a6f 100644 --- a/tests/suites/test_suite_x509parse.data +++ b/tests/suites/test_suite_x509parse.data @@ -2062,11 +2062,27 @@ mbedtls_x509_crt_parse_file:"../framework/data_files/parse_input/server5.basic-c X509 CRT ASN1: basicConstraints: no cA but pathLenConstraint (bad signature) depends_on:PSA_HAVE_ALG_ECDSA_VERIFY:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256 -mbedtls_x509_crt_parse_file:"../framework/data_files/parse_input/server5.basic-constraints-integer-first.badsign.crt":MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, MBEDTLS_ERR_ASN1_UNEXPECTED_TAG):0 +mbedtls_x509_crt_parse_file:"../framework/data_files/parse_input/server5.basic-constraints-integer-first.badsign.crt":0:1 + +X509 CRT ASN1: basicConstraints: no cA, pathLen only: not a CA +depends_on:PSA_HAVE_ALG_ECDSA_VERIFY:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256 +mbedtls_x509_get_ca_istrue:"../framework/data_files/parse_input/server5.basic-constraints-integer-first.badsign.crt":0 X509 CRT ASN1: basicConstraints: no cA but pathLenConstraint (good self signature) depends_on:PSA_HAVE_ALG_ECDSA_VERIFY:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256 -mbedtls_x509_crt_parse_file:"../framework/data_files/parse_input/server5.basic-constraints-integer-first.selfsigned.crt":MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, MBEDTLS_ERR_ASN1_UNEXPECTED_TAG):0 +mbedtls_x509_crt_parse_file:"../framework/data_files/parse_input/server5.basic-constraints-integer-first.selfsigned.crt":0:1 + +X509 CRT ASN1: basicConstraints: no cA but zero pathLenConstraint +depends_on:PSA_HAVE_ALG_ECDSA_VERIFY:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256 +x509_crt_parse_basic_constraints:"308201573081fea003020102020c53a2cb4b124ead837da894b2300a06082a8648ce3d040302300e310c300a06035504030c03457665301e170d3236303531323039353330345a170d3336303531323039353330345a300e310c300a06035504030c034576653059301306072a8648ce3d020106082a8648ce3d0301070342000437cc56d976091e5a723ec7592dff206eee7cf9069174d0ad14b5f768225962924ee500d82311ffea2fd2345d5d16bd8a88c26b770d55cd8a2a0efa01c8b4edffa3423040300f0603551d130101ff04053003020100300e0603551d0f0101ff040403020780301d0603551d0e041604145061a58fd407d9d782010ce5657f8c6346a713be300a06082a8648ce3d0403020348003045022100d67d572af0aa540cd0f9d86d43788c178ca9501d8c0f4f6690c773f4b82c4b9a022011620feee62aca3944b8321636849030014ef80604550031312bb707b7f2bf04":0:0:0 + +X509 CRT ASN1: basicConstraints: invalid cA BOOLEAN length +depends_on:PSA_HAVE_ALG_ECDSA_VERIFY:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256 +x509_crt_parse_basic_constraints:"308201573081fea003020102020c53a2cb4b124ead837da894b2300a06082a8648ce3d040302300e310c300a06035504030c03457665301e170d3236303531323039353330345a170d3336303531323039353330345a300e310c300a06035504030c034576653059301306072a8648ce3d020106082a8648ce3d0301070342000437cc56d976091e5a723ec7592dff206eee7cf9069174d0ad14b5f768225962924ee500d82311ffea2fd2345d5d16bd8a88c26b770d55cd8a2a0efa01c8b4edffa3423040300f0603551d130101ff040530030100ff300e0603551d0f0101ff040403020780301d0603551d0e041604145061a58fd407d9d782010ce5657f8c6346a713be300a06082a8648ce3d0403020348003045022100d67d572af0aa540cd0f9d86d43788c178ca9501d8c0f4f6690c773f4b82c4b9a022011620feee62aca3944b8321636849030014ef80604550031312bb707b7f2bf04":MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, MBEDTLS_ERR_ASN1_INVALID_LENGTH):0:0 + +X509 CRT ASN1: basicConstraints: neither cA nor pathLenConstraint +depends_on:PSA_HAVE_ALG_ECDSA_VERIFY:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256 +x509_crt_parse_basic_constraints:"308201573081fea003020102020c53a2cb4b124ead837da894b2300a06082a8648ce3d040302300e310c300a06035504030c03457665301e170d3236303531323039353330345a170d3336303531323039353330345a300e310c300a06035504030c034576653059301306072a8648ce3d020106082a8648ce3d0301070342000437cc56d976091e5a723ec7592dff206eee7cf9069174d0ad14b5f768225962924ee500d82311ffea2fd2345d5d16bd8a88c26b770d55cd8a2a0efa01c8b4edffa3423040300f0603551d130101ff04053003050100300e0603551d0f0101ff040403020780301d0603551d0e041604145061a58fd407d9d782010ce5657f8c6346a713be300a06082a8648ce3d0403020348003045022100d67d572af0aa540cd0f9d86d43788c178ca9501d8c0f4f6690c773f4b82c4b9a022011620feee62aca3944b8321636849030014ef80604550031312bb707b7f2bf04":MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, MBEDTLS_ERR_ASN1_UNEXPECTED_TAG):0:0 X509 CRT ASN1 (TBS, inv extBasicConstraint, no pathlen length) depends_on:PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY:PSA_WANT_ALG_SHA_256 diff --git a/tests/suites/test_suite_x509parse.function b/tests/suites/test_suite_x509parse.function index 25d229d6f1..e095cb2b83 100644 --- a/tests/suites/test_suite_x509parse.function +++ b/tests/suites/test_suite_x509parse.function @@ -1127,6 +1127,28 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_X509_CRT_PARSE_C */ +void x509_crt_parse_basic_constraints(data_t *buf, int result, + int ca_istrue, int path_len_constraint) +{ + mbedtls_x509_crt crt; + + mbedtls_x509_crt_init(&crt); + USE_PSA_INIT(); + + TEST_EQUAL(mbedtls_x509_crt_parse_der(&crt, buf->x, buf->len), result); + if (result == 0) { + TEST_EQUAL(mbedtls_x509_crt_get_ca_istrue(&crt), ca_istrue); + /* max_pathlen is 1 higher than pathLenConstraint. */ + TEST_EQUAL(crt.max_pathlen, path_len_constraint + 1); + } + +exit: + mbedtls_x509_crt_free(&crt); + USE_PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_X509_CRT_PARSE_C */ void x509parse_crt(data_t *buf, char *result_str, int result) {