mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-04 21:24:32 +02:00
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:
@@ -60,12 +60,22 @@ if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)
|
|||||||
set(GENERATE_CERT_BUNDLEPY ${python} ${COMPONENT_DIR}/esp_crt_bundle/gen_crt_bundle.py)
|
set(GENERATE_CERT_BUNDLEPY ${python} ${COMPONENT_DIR}/esp_crt_bundle/gen_crt_bundle.py)
|
||||||
|
|
||||||
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL)
|
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)
|
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)
|
list(APPEND args --filter ${DEFAULT_CRT_DIR}/cmn_crt_authorities.csv)
|
||||||
endif()
|
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
|
# Add deprecated root certs if enabled. This config is not visible if the default cert
|
||||||
# bundle is not selected
|
# bundle is not selected
|
||||||
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST)
|
if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST)
|
||||||
|
@@ -367,6 +367,17 @@ menu "mbedTLS"
|
|||||||
default 200
|
default 200
|
||||||
depends on MBEDTLS_CERTIFICATE_BUNDLE
|
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
|
endmenu
|
||||||
|
|
||||||
config MBEDTLS_ECP_RESTARTABLE
|
config MBEDTLS_ECP_RESTARTABLE
|
||||||
|
@@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
#include "mbedtls/oid.h"
|
#include "mbedtls/oid.h"
|
||||||
#include "mbedtls/asn1.h"
|
#include "mbedtls/asn1.h"
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Format of certificate bundle:
|
Format of certificate bundle:
|
||||||
First, n uint32 "offset" entries, each describing the start of one certificate's data in terms of
|
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
|
/* a dummy certificate so that
|
||||||
* cacert_ptr passes non-NULL check during handshake */
|
* 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;
|
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_start[] asm("_binary_x509_crt_bundle_start");
|
||||||
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
|
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 esp_crt_bundle_attach(void *conf)
|
||||||
{
|
{
|
||||||
esp_err_t ret = ESP_OK;
|
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
|
* cacert_ptr passes non-NULL check during handshake
|
||||||
*/
|
*/
|
||||||
mbedtls_ssl_config *ssl_conf = (mbedtls_ssl_config *)conf;
|
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);
|
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;
|
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)
|
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);
|
return ((ca_chain == &s_dummy_crt) ? true : false);
|
||||||
|
#endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY
|
||||||
}
|
}
|
||||||
|
@@ -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.
|
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
|
Application Examples
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@@ -10,3 +10,4 @@ CONFIG_EXAMPLE_CONNECT_IPV6=y
|
|||||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
|
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
|
||||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y
|
CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y
|
||||||
CONFIG_EXAMPLE_HTTP_ENDPOINT="httpbin.espressif.cn"
|
CONFIG_EXAMPLE_HTTP_ENDPOINT="httpbin.espressif.cn"
|
||||||
|
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y
|
||||||
|
Reference in New Issue
Block a user