Merge branch 'fix/crt_bundle_unsupported_cert_extension' into 'master'

fix(mbedtls):report an error for certificate files with unsupported extension

See merge request espressif/esp-idf!51518
This commit is contained in:
Mahavir Jain
2026-08-12 13:54:53 +05:30
6 changed files with 97 additions and 4 deletions
+5
View File
@@ -516,6 +516,11 @@ menu "mbedTLS"
Name of the custom certificate directory or file. This path is evaluated Name of the custom certificate directory or file. This path is evaluated
relative to the project root directory. relative to the project root directory.
Certificates must be either PEM encoded with a .pem extension, or DER
encoded with a .der extension. Files with any other extension are not
parsed: pointing this option directly at such a file fails the build,
while such files inside a directory are skipped with a warning.
config MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST config MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST
bool "Add deprecated root certificates" bool "Add deprecated root certificates"
depends on MBEDTLS_CERTIFICATE_BUNDLE && !MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE depends on MBEDTLS_CERTIFICATE_BUNDLE && !MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE
@@ -46,6 +46,10 @@ except ImportError:
ca_bundle_bin_file = 'x509_crt_bundle' ca_bundle_bin_file = 'x509_crt_bundle'
# Only files with these extensions are parsed, the extension has to match the encoding of the file
PEM_FILE_EXTENSION = '.pem'
DER_FILE_EXTENSION = '.der'
quiet = False quiet = False
@@ -70,27 +74,44 @@ class CertificateBundle:
def add_from_path(self, crts_path): def add_from_path(self, crts_path):
found = False found = False
for file_path in os.listdir(crts_path): for file_path in os.listdir(crts_path):
found |= self.add_from_file(os.path.join(crts_path, file_path)) found |= self.add_from_file(os.path.join(crts_path, file_path), strict=False)
if found is False: if found is False:
raise InputError('No valid x509 certificates found in %s' % crts_path) raise InputError('No valid x509 certificates found in %s' % crts_path)
def add_from_file(self, file_path): def add_from_file(self, file_path, strict=True):
"""Parse a certificate file
Only files with a .pem or .der extension are parsed. If strict is True (the file was
given directly on the command line), any other extension is an error, otherwise
(while scanning a directory) the file is skipped with a warning.
"""
try: try:
if file_path.endswith('.pem'): if file_path.endswith(PEM_FILE_EXTENSION):
status('Parsing certificates from %s' % file_path) status('Parsing certificates from %s' % file_path)
with open(file_path, 'r', encoding='utf-8') as f: with open(file_path, 'r', encoding='utf-8') as f:
crt_str = f.read() crt_str = f.read()
self.add_from_pem(crt_str) self.add_from_pem(crt_str)
return True return True
elif file_path.endswith('.der'): elif file_path.endswith(DER_FILE_EXTENSION):
status('Parsing certificates from %s' % file_path) status('Parsing certificates from %s' % file_path)
with open(file_path, 'rb') as f: with open(file_path, 'rb') as f:
crt_str = f.read() crt_str = f.read()
self.add_from_der(crt_str) self.add_from_der(crt_str)
return True return True
elif os.path.isfile(file_path):
msg = (
'Unsupported file extension in %s, certificates are only parsed from files with a '
'%s (PEM encoded) or %s (DER encoded) extension. '
'Please rename the file to match its encoding.'
% (file_path, PEM_FILE_EXTENSION, DER_FILE_EXTENSION)
)
if strict:
raise InputError(msg)
critical('Warning: skipping file. %s' % msg)
except ValueError: except ValueError:
critical('Invalid certificate in %s' % file_path) critical('Invalid certificate in %s' % file_path)
raise InputError('Invalid certificate') raise InputError('Invalid certificate')
@@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
import os import os
import shutil
import sys import sys
import tempfile
import unittest import unittest
try: try:
@@ -22,6 +24,7 @@ verified_der_bundle = 'baltimore_crt_bundle'
verified_pem_bundle = 'entrust_crt_bundle' verified_pem_bundle = 'entrust_crt_bundle'
invalid_test_file = 'invalid_crt.pem' invalid_test_file = 'invalid_crt.pem'
non_ascii_file = 'non_ascii_crt.pem' non_ascii_file = 'non_ascii_crt.pem'
unsupported_file = 'unsupported_ext_crt.crt'
ca_crts_all_file = 'cacrt_all.pem' ca_crts_all_file = 'cacrt_all.pem'
@@ -73,6 +76,37 @@ class GenCrtBundleTests(Py23TestCase):
bundle.add_from_pem('') bundle.add_from_pem('')
self.assertEqual(len(bundle.certificates), bundle_prev_len) self.assertEqual(len(bundle.certificates), bundle_prev_len)
# A file given directly is expected to be a certificate, so an unknown extension is an error
def test_unsupported_extension_input(self):
bundle = gen_crt_bundle.CertificateBundle()
with self.assertRaisesRegex(gen_crt_bundle.InputError, 'Unsupported file extension'):
bundle.add_from_file(test_crts_path + unsupported_file)
self.assertEqual(len(bundle.certificates), 0)
# While scanning a directory, files with an unknown extension are skipped instead
def test_unsupported_extension_in_dir(self):
bundle = gen_crt_bundle.CertificateBundle()
self.assertFalse(bundle.add_from_file(test_crts_path + unsupported_file, strict=False))
self.assertEqual(len(bundle.certificates), 0)
# A directory holding both valid and unsupported files adds only the valid ones
with tempfile.TemporaryDirectory() as crts_dir:
shutil.copy(test_crts_path + unsupported_file, crts_dir)
shutil.copy(test_crts_path + pem_test_file, crts_dir)
bundle.add_from_path(crts_dir)
self.assertEqual(len(bundle.certificates), 1)
# A directory without any supported file is an error
with tempfile.TemporaryDirectory() as crts_dir:
shutil.copy(test_crts_path + unsupported_file, crts_dir)
with self.assertRaisesRegex(gen_crt_bundle.InputError, 'No valid x509 certificates found'):
bundle.add_from_path(crts_dir)
def test_non_ascii_crt_input(self): def test_non_ascii_crt_input(self):
bundle = gen_crt_bundle.CertificateBundle() bundle = gen_crt_bundle.CertificateBundle()
@@ -0,0 +1,25 @@
Entrust Root Certification Authority
====================================
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----
@@ -30,6 +30,10 @@ Most configuration is done through menuconfig. CMake generates the bundle accord
* :menuitem:`CONFIG_MBEDTLS_DEFAULT_CERTIFICATE_BUNDLE`: decide which certificates to include from the complete root certificate list. * :menuitem:`CONFIG_MBEDTLS_DEFAULT_CERTIFICATE_BUNDLE`: decide which certificates to include from the complete root certificate list.
* :menuitem:`CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH`: specify the path of any additional certificates to embed in the bundle. * :menuitem:`CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH`: specify the path of any additional certificates to embed in the bundle.
.. note::
Only PEM encoded certificates with a ``.pem`` extension and DER encoded certificates with a ``.der`` extension are parsed. The extension must match the encoding of the file, for example a PEM encoded certificate saved as ``.crt`` is not accepted. If :ref:`CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH` points directly at a file with any other extension, the build fails; if such a file is found inside a certificate directory, it is skipped and a warning is printed.
To enable the bundle when using ESP-TLS simply pass the function pointer to the bundle attach function: To enable the bundle when using ESP-TLS simply pass the function pointer to the bundle attach function:
.. code-block:: c .. code-block:: c
@@ -30,6 +30,10 @@ ESP x509 证书包 API 提供了一种简便的方法,帮助你安装自定义
* :menuitem:`CONFIG_MBEDTLS_DEFAULT_CERTIFICATE_BUNDLE`:决定添加证书列表中的哪些证书。 * :menuitem:`CONFIG_MBEDTLS_DEFAULT_CERTIFICATE_BUNDLE`:决定添加证书列表中的哪些证书。
* :menuitem:`CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH`:指定要在证书包中嵌入的其他证书的路径。 * :menuitem:`CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH`:指定要在证书包中嵌入的其他证书的路径。
.. note::
只有扩展名为 ``.pem`` 的 PEM 编码证书和扩展名为 ``.der`` 的 DER 编码证书会被解析。扩展名必须与文件的编码格式一致,例如保存为 ``.crt`` 的 PEM 编码证书不会被接受。如果 :ref:`CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH` 直接指向其他扩展名的文件,构建将失败;如果证书目录中存在此类文件,该文件会被跳过并打印警告。
要在使用 ESP-TLS 时启用证书包,将函数指针指向证书包的 attach 函数: 要在使用 ESP-TLS 时启用证书包,将函数指针指向证书包的 attach 函数:
.. code-block:: c .. code-block:: c