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
This commit is contained in:
Mahavir Jain
2025-06-30 10:15:54 +05:30
5 changed files with 183 additions and 4 deletions

View File

@@ -60,12 +60,22 @@ if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)
set(GENERATE_CERT_BUNDLEPY ${python} ${COMPONENT_DIR}/esp_crt_bundle/gen_crt_bundle.py)
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL)
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem ${DEFAULT_CRT_DIR}/cacrt_local.pem)
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem)
elseif(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN)
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem ${DEFAULT_CRT_DIR}/cacrt_local.pem)
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem)
list(APPEND args --filter ${DEFAULT_CRT_DIR}/cmn_crt_authorities.csv)
endif()
# Currently cacrt_local.pem contains deprecated certificates that are still required for certain certificate chains.
# These chains may include cross-signed certificates, but the final certificate in the chain is deprecated.
# When cross-signed verification is enabled (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY),
# the cross-signed certificate should be sufficient for verification, and the deprecated root is not needed.
# Therefore, cacrt_local.pem is only appended if cross-signed verification is not enabled.
if((CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL OR CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN)
AND NOT CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_local.pem)
endif()
# Add deprecated root certs if enabled. This config is not visible if the default cert
# bundle is not selected
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST)

View File

@@ -367,6 +367,17 @@ menu "mbedTLS"
default 200
depends on MBEDTLS_CERTIFICATE_BUNDLE
config MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY
bool "Support cross-signed certificate verification in certificate bundle"
default n
depends on MBEDTLS_CERTIFICATE_BUNDLE
select MBEDTLS_X509_TRUSTED_CERT_CALLBACK
help
Enable support for cross-signed certificate verification in the certificate bundle.
This feature uses an internal callback to verify the cross-signed certificates.
This feature is kept disabled by default as enabling this feature increases
heap usage by approximately 700 bytes.
endmenu
config MBEDTLS_ECP_RESTARTABLE

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -15,6 +15,8 @@
#include "mbedtls/oid.h"
#include "mbedtls/asn1.h"
#include "sdkconfig.h"
/*
Format of certificate bundle:
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";
/* a dummy certificate so that
* cacert_ptr passes non-NULL check during handshake */
#if !defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
static const mbedtls_x509_crt s_dummy_crt;
#endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
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
}
}
#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
static int esp_crt_copy_asn1(const mbedtls_asn1_named_data *src, mbedtls_asn1_named_data *dst)
{
if (src == NULL || dst == NULL) {
return -1;
}
dst->oid.tag = src->oid.tag;
dst->oid.len = src->oid.len;
dst->oid.p = calloc(1, src->oid.len);
if (dst->oid.p == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for OID");
return -1;
}
memcpy(dst->oid.p, src->oid.p, src->oid.len);
dst->val.tag = src->val.tag;
dst->val.len = src->val.len;
dst->val.p = calloc(1, src->val.len);
if (dst->val.p == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for value");
free(dst->oid.p);
return -1;
}
memcpy(dst->val.p, src->val.p, src->val.len);
return 0;
}
static int esp_crt_ca_cb_callback(void *ctx, mbedtls_x509_crt const *child, mbedtls_x509_crt **candidate_cas)
{
if (unlikely(s_crt_bundle == NULL)) {
ESP_LOGE(TAG, "No certificates in bundle");
return MBEDTLS_ERR_X509_FATAL_ERROR;
}
ESP_LOGD(TAG, "%" PRIu16 " certificates in bundle", (uint16_t)esp_crt_get_certcount(s_crt_bundle));
cert_t cert = esp_crt_find_cert(child->issuer_raw.p, child->issuer_raw.len);
if (likely(cert == NULL)) {
*candidate_cas = NULL;
return 0;
}
// If we found a matching certificate, we need to allocate a new
// mbedtls_x509_crt structure and copy the certificate data into it.
mbedtls_x509_crt *new_cert = calloc(1, sizeof(mbedtls_x509_crt));
if (unlikely(new_cert == NULL)) {
ESP_LOGE(TAG, "Failed to allocate memory for new certificate");
return MBEDTLS_ERR_X509_ALLOC_FAILED;
}
mbedtls_x509_crt_init(new_cert);
new_cert->MBEDTLS_PRIVATE(ca_istrue) = true;
new_cert->version = 3;
const uint8_t *cert_name = esp_crt_get_name(cert);
uint16_t cert_name_len = esp_crt_get_name_len(cert);
new_cert->subject_raw.tag = MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE;
new_cert->subject_raw.len = cert_name_len;
new_cert->subject_raw.p = calloc(1, cert_name_len);
if (new_cert->subject_raw.p == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for subject");
mbedtls_x509_crt_free(new_cert);
free(new_cert);
return MBEDTLS_ERR_X509_ALLOC_FAILED;
}
memcpy(new_cert->subject_raw.p, cert_name, cert_name_len);
const uint8_t *cert_key = esp_crt_get_key(cert);
uint16_t cert_key_len = esp_crt_get_key_len(cert);
// Set the public key in the new certificate
mbedtls_pk_init(&new_cert->pk);
int ret = mbedtls_pk_parse_subpubkey((unsigned char **)&cert_key, cert_key + cert_key_len, &new_cert->pk);
if (ret != 0) {
ESP_LOGE(TAG, "Failed to parse public key from certificate: %d", ret);
mbedtls_x509_crt_free(new_cert);
free(new_cert);
return ret;
}
// Loop through the child->issuer and copy the values to the new certificate
const mbedtls_asn1_named_data *child_issuer = &child->issuer;
mbedtls_asn1_named_data *parent_subject = &new_cert->subject;
while (child_issuer != NULL) {
if (esp_crt_copy_asn1(child_issuer, parent_subject) != 0) {
ESP_LOGE(TAG, "Failed to copy ASN.1 data");
mbedtls_x509_crt_free(new_cert);
free(new_cert);
return MBEDTLS_ERR_X509_ALLOC_FAILED;
}
child_issuer = child_issuer->next;
if (child_issuer == NULL) {
break;
}
if (parent_subject->next == NULL) {
parent_subject->next = calloc(1, sizeof(mbedtls_asn1_named_data));
if (parent_subject->next == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for next issuer");
mbedtls_x509_crt_free(new_cert);
free(new_cert);
return MBEDTLS_ERR_X509_ALLOC_FAILED;
}
parent_subject = parent_subject->next;
}
}
// Set the parsed certificate as the candidate CA
*candidate_cas = new_cert;
return 0;
}
#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */
esp_err_t esp_crt_bundle_attach(void *conf)
{
esp_err_t ret = ESP_OK;
@@ -349,8 +465,12 @@ esp_err_t esp_crt_bundle_attach(void *conf)
* cacert_ptr passes non-NULL check during handshake
*/
mbedtls_ssl_config *ssl_conf = (mbedtls_ssl_config *)conf;
mbedtls_ssl_conf_ca_chain(ssl_conf, (mbedtls_x509_crt*)&s_dummy_crt, NULL);
mbedtls_ssl_conf_verify(ssl_conf, esp_crt_verify_callback, NULL);
#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
mbedtls_ssl_conf_ca_cb(ssl_conf, esp_crt_ca_cb_callback, NULL);
#else
mbedtls_ssl_conf_ca_chain(ssl_conf, (mbedtls_x509_crt*)&s_dummy_crt, NULL);
#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */
}
return ret;
@@ -371,5 +491,9 @@ esp_err_t esp_crt_bundle_set(const uint8_t *x509_bundle, size_t bundle_size)
bool esp_crt_bundle_in_use(const mbedtls_x509_crt* ca_chain)
{
#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY)
return false; // Cross-signed verification does not use the dummy certificate
#else
return ((ca_chain == &s_dummy_crt) ? true : false);
#endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY
}

View File

@@ -81,6 +81,39 @@ Periodic Sync
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.
Cross-Signed Certificate Support
---------------------------------
Overview
^^^^^^^^
When the configuration option :ref:`CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY` is enabled,
the ESP x509 Certificate Bundle API adds support for verifying certificate chains that include cross-signed root certificates.
This feature allows the verification process to dynamically select candidate Certificate Authorities (CAs) from the bundle,
even when the certificate chain contains cross-signed roots, improving interoperability with a wider range of server certificates.
With this functionality enabled, certificate verification is performed in a manner equivalent to the default mbedTLS behaviour,
ensuring compatibility and robust validation for cross-signed chains.
.. note::
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.
Key Points:
- The bundle can act as a dynamic CA store, providing candidate root certificates during the handshake.
- The verification callback uses the issuer information from the certificate chain to locate and provide matching root certificates from the bundle.
- This is especially useful for environments where cross-signing is common, such as during root CA transitions.
Usage
^^^^^
No additional application changes are required beyond enabling :ref:`CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY` in your project configuration.
The bundle will automatically provide candidate CAs during the TLS handshake.
.. note::
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.
Application Examples
--------------------

View File

@@ -10,3 +10,4 @@ CONFIG_EXAMPLE_CONNECT_IPV6=y
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y
CONFIG_EXAMPLE_HTTP_ENDPOINT="httpbin.espressif.cn"
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y