Skip to content

Commit 1f4612e

Browse files
committed
Merge branch 'feat/support_cross_signed_root_certs' into 'master'
feat(esp_crt): adds support for cross signed root certificates Closes IDF-13364 See merge request espressif/esp-idf!39797
2 parents 7f7d0af + cabb500 commit 1f4612e

File tree

5 files changed

+183
-4
lines changed

5 files changed

+183
-4
lines changed

components/mbedtls/CMakeLists.txt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,22 @@ if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)
6060
set(GENERATE_CERT_BUNDLEPY ${python} ${COMPONENT_DIR}/esp_crt_bundle/gen_crt_bundle.py)
6161

6262
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL)
63-
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem ${DEFAULT_CRT_DIR}/cacrt_local.pem)
63+
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem)
6464
elseif(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN)
65-
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem ${DEFAULT_CRT_DIR}/cacrt_local.pem)
65+
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem)
6666
list(APPEND args --filter ${DEFAULT_CRT_DIR}/cmn_crt_authorities.csv)
6767
endif()
6868

69+
# Currently cacrt_local.pem contains deprecated certificates that are still required for certain certificate chains.
70+
# These chains may include cross-signed certificates, but the final certificate in the chain is deprecated.
71+
# When cross-signed verification is enabled (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY),
72+
# the cross-signed certificate should be sufficient for verification, and the deprecated root is not needed.
73+
# Therefore, cacrt_local.pem is only appended if cross-signed verification is not enabled.
74+
if((CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL OR CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN)
75+
AND NOT CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
76+
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_local.pem)
77+
endif()
78+
6979
# Add deprecated root certs if enabled. This config is not visible if the default cert
7080
# bundle is not selected
7181
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST)

components/mbedtls/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,17 @@ menu "mbedTLS"
367367
default 200
368368
depends on MBEDTLS_CERTIFICATE_BUNDLE
369369

370+
config MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY
371+
bool "Support cross-signed certificate verification in certificate bundle"
372+
default n
373+
depends on MBEDTLS_CERTIFICATE_BUNDLE
374+
select MBEDTLS_X509_TRUSTED_CERT_CALLBACK
375+
help
376+
Enable support for cross-signed certificate verification in the certificate bundle.
377+
This feature uses an internal callback to verify the cross-signed certificates.
378+
This feature is kept disabled by default as enabling this feature increases
379+
heap usage by approximately 700 bytes.
380+
370381
endmenu
371382

372383
config MBEDTLS_ECP_RESTARTABLE

components/mbedtls/esp_crt_bundle/esp_crt_bundle.c

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -15,6 +15,8 @@
1515
#include "mbedtls/oid.h"
1616
#include "mbedtls/asn1.h"
1717

18+
#include "sdkconfig.h"
19+
1820
/*
1921
Format of certificate bundle:
2022
First, n uint32 "offset" entries, each describing the start of one certificate's data in terms of
@@ -54,7 +56,9 @@ static const char *TAG = "esp-x509-crt-bundle";
5456

5557
/* a dummy certificate so that
5658
* cacert_ptr passes non-NULL check during handshake */
59+
#if !defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
5760
static const mbedtls_x509_crt s_dummy_crt;
61+
#endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY
5862

5963
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
6064
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
@@ -330,6 +334,118 @@ static esp_err_t esp_crt_bundle_init(const uint8_t* const x509_bundle, const siz
330334
}
331335
}
332336

337+
#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
338+
static int esp_crt_copy_asn1(const mbedtls_asn1_named_data *src, mbedtls_asn1_named_data *dst)
339+
{
340+
if (src == NULL || dst == NULL) {
341+
return -1;
342+
}
343+
344+
dst->oid.tag = src->oid.tag;
345+
dst->oid.len = src->oid.len;
346+
dst->oid.p = calloc(1, src->oid.len);
347+
if (dst->oid.p == NULL) {
348+
ESP_LOGE(TAG, "Failed to allocate memory for OID");
349+
return -1;
350+
}
351+
memcpy(dst->oid.p, src->oid.p, src->oid.len);
352+
dst->val.tag = src->val.tag;
353+
dst->val.len = src->val.len;
354+
dst->val.p = calloc(1, src->val.len);
355+
if (dst->val.p == NULL) {
356+
ESP_LOGE(TAG, "Failed to allocate memory for value");
357+
free(dst->oid.p);
358+
return -1;
359+
}
360+
memcpy(dst->val.p, src->val.p, src->val.len);
361+
return 0;
362+
}
363+
364+
static int esp_crt_ca_cb_callback(void *ctx, mbedtls_x509_crt const *child, mbedtls_x509_crt **candidate_cas)
365+
{
366+
if (unlikely(s_crt_bundle == NULL)) {
367+
ESP_LOGE(TAG, "No certificates in bundle");
368+
return MBEDTLS_ERR_X509_FATAL_ERROR;
369+
}
370+
371+
ESP_LOGD(TAG, "%" PRIu16 " certificates in bundle", (uint16_t)esp_crt_get_certcount(s_crt_bundle));
372+
373+
cert_t cert = esp_crt_find_cert(child->issuer_raw.p, child->issuer_raw.len);
374+
375+
if (likely(cert == NULL)) {
376+
*candidate_cas = NULL;
377+
return 0;
378+
}
379+
// If we found a matching certificate, we need to allocate a new
380+
// mbedtls_x509_crt structure and copy the certificate data into it.
381+
mbedtls_x509_crt *new_cert = calloc(1, sizeof(mbedtls_x509_crt));
382+
if (unlikely(new_cert == NULL)) {
383+
ESP_LOGE(TAG, "Failed to allocate memory for new certificate");
384+
return MBEDTLS_ERR_X509_ALLOC_FAILED;
385+
}
386+
mbedtls_x509_crt_init(new_cert);
387+
388+
new_cert->MBEDTLS_PRIVATE(ca_istrue) = true;
389+
new_cert->version = 3;
390+
391+
const uint8_t *cert_name = esp_crt_get_name(cert);
392+
uint16_t cert_name_len = esp_crt_get_name_len(cert);
393+
new_cert->subject_raw.tag = MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE;
394+
new_cert->subject_raw.len = cert_name_len;
395+
new_cert->subject_raw.p = calloc(1, cert_name_len);
396+
if (new_cert->subject_raw.p == NULL) {
397+
ESP_LOGE(TAG, "Failed to allocate memory for subject");
398+
mbedtls_x509_crt_free(new_cert);
399+
free(new_cert);
400+
return MBEDTLS_ERR_X509_ALLOC_FAILED;
401+
}
402+
memcpy(new_cert->subject_raw.p, cert_name, cert_name_len);
403+
404+
const uint8_t *cert_key = esp_crt_get_key(cert);
405+
uint16_t cert_key_len = esp_crt_get_key_len(cert);
406+
// Set the public key in the new certificate
407+
mbedtls_pk_init(&new_cert->pk);
408+
int ret = mbedtls_pk_parse_subpubkey((unsigned char **)&cert_key, cert_key + cert_key_len, &new_cert->pk);
409+
if (ret != 0) {
410+
ESP_LOGE(TAG, "Failed to parse public key from certificate: %d", ret);
411+
mbedtls_x509_crt_free(new_cert);
412+
free(new_cert);
413+
return ret;
414+
}
415+
416+
// Loop through the child->issuer and copy the values to the new certificate
417+
const mbedtls_asn1_named_data *child_issuer = &child->issuer;
418+
mbedtls_asn1_named_data *parent_subject = &new_cert->subject;
419+
while (child_issuer != NULL) {
420+
if (esp_crt_copy_asn1(child_issuer, parent_subject) != 0) {
421+
ESP_LOGE(TAG, "Failed to copy ASN.1 data");
422+
mbedtls_x509_crt_free(new_cert);
423+
free(new_cert);
424+
return MBEDTLS_ERR_X509_ALLOC_FAILED;
425+
}
426+
child_issuer = child_issuer->next;
427+
if (child_issuer == NULL) {
428+
break;
429+
}
430+
431+
if (parent_subject->next == NULL) {
432+
parent_subject->next = calloc(1, sizeof(mbedtls_asn1_named_data));
433+
if (parent_subject->next == NULL) {
434+
ESP_LOGE(TAG, "Failed to allocate memory for next issuer");
435+
mbedtls_x509_crt_free(new_cert);
436+
free(new_cert);
437+
return MBEDTLS_ERR_X509_ALLOC_FAILED;
438+
}
439+
parent_subject = parent_subject->next;
440+
}
441+
}
442+
443+
// Set the parsed certificate as the candidate CA
444+
*candidate_cas = new_cert;
445+
return 0;
446+
}
447+
#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */
448+
333449
esp_err_t esp_crt_bundle_attach(void *conf)
334450
{
335451
esp_err_t ret = ESP_OK;
@@ -349,8 +465,12 @@ esp_err_t esp_crt_bundle_attach(void *conf)
349465
* cacert_ptr passes non-NULL check during handshake
350466
*/
351467
mbedtls_ssl_config *ssl_conf = (mbedtls_ssl_config *)conf;
352-
mbedtls_ssl_conf_ca_chain(ssl_conf, (mbedtls_x509_crt*)&s_dummy_crt, NULL);
353468
mbedtls_ssl_conf_verify(ssl_conf, esp_crt_verify_callback, NULL);
469+
#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
470+
mbedtls_ssl_conf_ca_cb(ssl_conf, esp_crt_ca_cb_callback, NULL);
471+
#else
472+
mbedtls_ssl_conf_ca_chain(ssl_conf, (mbedtls_x509_crt*)&s_dummy_crt, NULL);
473+
#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */
354474
}
355475

356476
return ret;
@@ -371,5 +491,9 @@ esp_err_t esp_crt_bundle_set(const uint8_t *x509_bundle, size_t bundle_size)
371491

372492
bool esp_crt_bundle_in_use(const mbedtls_x509_crt* ca_chain)
373493
{
494+
#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
495+
return false; // Cross-signed verification does not use the dummy certificate
496+
#else
374497
return ((ca_chain == &s_dummy_crt) ? true : false);
498+
#endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY
375499
}

docs/en/api-reference/protocols/esp_crt_bundle.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,39 @@ Periodic Sync
8181

8282
The bundle is kept updated by periodic sync with the Mozilla's NSS root certificate store. The deprecated certs from the upstream bundle are added to deprecated list (for compatibility reasons) in ESP-IDF minor or patch release. If required, the deprecated certs can be added to the default bundle by enabling :ref:`CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST`. The deprecated certs shall be removed (reset) on the next major ESP-IDF release.
8383

84+
Cross-Signed Certificate Support
85+
---------------------------------
86+
87+
Overview
88+
^^^^^^^^
89+
90+
When the configuration option :ref:`CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY` is enabled,
91+
the ESP x509 Certificate Bundle API adds support for verifying certificate chains that include cross-signed root certificates.
92+
This feature allows the verification process to dynamically select candidate Certificate Authorities (CAs) from the bundle,
93+
even when the certificate chain contains cross-signed roots, improving interoperability with a wider range of server certificates.
94+
95+
With this functionality enabled, certificate verification is performed in a manner equivalent to the default mbedTLS behaviour,
96+
ensuring compatibility and robust validation for cross-signed chains.
97+
98+
.. note::
99+
100+
Enabling cross-signed certificate support increases run-time heap utilisation by approximately 700 bytes, but reduces the flash footprint as the bundle size is reduced.
101+
102+
Key Points:
103+
- The bundle can act as a dynamic CA store, providing candidate root certificates during the handshake.
104+
- The verification callback uses the issuer information from the certificate chain to locate and provide matching root certificates from the bundle.
105+
- This is especially useful for environments where cross-signing is common, such as during root CA transitions.
106+
107+
Usage
108+
^^^^^
109+
110+
No additional application changes are required beyond enabling :ref:`CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY` in your project configuration.
111+
The bundle will automatically provide candidate CAs during the TLS handshake.
112+
113+
.. note::
114+
115+
If :ref:`CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY` is enabled, it internally uses ``MBEDTLS_X509_TRUSTED_CERT_CALLBACK``. In this case, users should **not** provide their own trusted certificate callback, as the certificate bundle will manage this automatically.
116+
84117
Application Examples
85118
--------------------
86119

examples/protocols/esp_http_client/sdkconfig.ci

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ CONFIG_EXAMPLE_CONNECT_IPV6=y
1010
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
1111
CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y
1212
CONFIG_EXAMPLE_HTTP_ENDPOINT="httpbin.espressif.cn"
13+
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y

0 commit comments

Comments
 (0)