diff --git a/components/mbedtls/CMakeLists.txt b/components/mbedtls/CMakeLists.txt index 93cb165b43..94033218ce 100644 --- a/components/mbedtls/CMakeLists.txt +++ b/components/mbedtls/CMakeLists.txt @@ -61,7 +61,7 @@ if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE) list(APPEND crt_paths ${custom_bundle_path}) endif() - list(APPEND args --input ${crt_paths} -q) + list(APPEND args --input ${crt_paths} -q --max-certs "${CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS}") get_filename_component(crt_bundle ${bundle_name} diff --git a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c index 2da3850087..dc75d0cff4 100644 --- a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c +++ b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c @@ -1,15 +1,54 @@ /* - * SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include +#include + +#include "esp_check.h" #include "esp_crt_bundle.h" #include "esp_log.h" -#define BUNDLE_HEADER_OFFSET 2 -#define CRT_HEADER_OFFSET 4 +#include "mbedtls/pk.h" +#include "mbedtls/oid.h" +#include "mbedtls/asn1.h" + +/* + Format of certificate bundle: + First, n uint32 "offset" entries, each describing the start of one certificate's data in terms of + bytes from the beginning of the bundle. This offset list is immediately followed by the 1st...n-th + certificate data. Hence, the first offset entry, i.e. the uint32 at the very start of the bundle, + is equal to the size of the offset list in bytes and therefore the # of certificates in the bundle + is [first offset]/sizeof(uint32_t) + [offset of 1st certificate](u32) + [offset of 2nd certificate](u32) + ... + [offset of n-th certificate](u32) + [1st certificate](variable) + ... + [n-th certificate](variable) + + Structure of each certificate: + [length of CN](u16) + [length of key](u16) + [CN](variable) + [key](variable) + + The offset list is used for fast random access to any certificate by index. + For verification, a certificate is looked up by its CN via binary search; for this reason, + the offset list *must* be sorted by CN (ascending) and the first certificate must be the + one with the least CN in the bundle, so that the first offset in the list still refers to the + first certificate after the list (see above). + +*/ + +#define CRT_NAME_LEN_OFFSET 0 //MBEDTLS_PRIVATE(sig_pk))) { - ESP_LOGE(TAG, "Simple compare failed"); - ret = -1; + if (unlikely(!mbedtls_pk_can_do(&pubkey, child->MBEDTLS_PRIVATE(sig_pk)))) { + ESP_LOGE(TAG, "Unsuitable public key"); + ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; goto cleanup; } md_info = mbedtls_md_info_from_type(child->MBEDTLS_PRIVATE(sig_md)); - if ( (ret = mbedtls_md( md_info, child->tbs.p, child->tbs.len, hash )) != 0 ) { - ESP_LOGE(TAG, "Internal mbedTLS error %X", ret); + + if (unlikely(md_info == NULL)) { + ESP_LOGE(TAG, "Unknown message digest"); + ret = MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE; goto cleanup; } - if ( (ret = mbedtls_pk_verify_ext( child->MBEDTLS_PRIVATE(sig_pk), child->MBEDTLS_PRIVATE(sig_opts), &parent.pk, - child->MBEDTLS_PRIVATE(sig_md), hash, mbedtls_md_get_size( md_info ), - child->MBEDTLS_PRIVATE(sig).p, child->MBEDTLS_PRIVATE(sig).len )) != 0 ) { + unsigned char hash[MBEDTLS_MD_MAX_SIZE]; + const unsigned char md_size = mbedtls_md_get_size(md_info); - ESP_LOGE(TAG, "PK verify failed with error %X", ret); + if ((ret = mbedtls_md(md_info, child->tbs.p, child->tbs.len, hash)) != 0) { + ESP_LOGE(TAG, "MD failed with error 0x%x", -ret); goto cleanup; } + + if (unlikely((ret = mbedtls_pk_verify_ext(child->MBEDTLS_PRIVATE(sig_pk), child->MBEDTLS_PRIVATE(sig_opts), &pubkey, + child->MBEDTLS_PRIVATE(sig_md), hash, md_size, + child->MBEDTLS_PRIVATE(sig).p, child->MBEDTLS_PRIVATE(sig).len)) != 0)) { + ESP_LOGE(TAG, "PK verify failed with error 0x%x", -ret); + goto cleanup; + } + cleanup: - mbedtls_x509_crt_free(&parent); - + mbedtls_pk_free(&pubkey); return ret; } +static cert_t esp_crt_find_cert(const unsigned char* const issuer, const size_t issuer_len) +{ + if (unlikely(issuer == NULL || issuer_len == 0)) { + return NULL; + } + + int start = 0; + int end = esp_crt_get_certcount(s_crt_bundle) - 1; + int middle = (start + end) / 2; + + cert_t cert = NULL; + size_t cert_name_len = 0; + + /* Look for the certificate using binary search on subject name */ + while (start <= end) { + cert = esp_crt_get_cert(s_crt_bundle, middle); + cert_name_len = esp_crt_get_name_len(cert); + + // Issuers are in DER encoding, with lengths encoded in the content; if valid DER, differing lengths + // are reflected in differing content. + // Still, we won't try to memcmp beyond the given length: + int cmp_res = memcmp(issuer, esp_crt_get_name(cert), MIN(issuer_len, cert_name_len)); + + if (unlikely(cmp_res == 0)) { + return cert; + } else if (cmp_res < 0) { + end = middle - 1; + } else { + start = middle + 1; + } + middle = (start + end) / 2; + } + + return NULL; +} + /* This callback is called for every certificate in the chain. If the chain * is proper each intermediate certificate is validated through its parent @@ -81,9 +213,9 @@ cleanup: * only verify the first untrusted link in the chain is signed by the * root certificate in the trusted bundle */ -int esp_crt_verify_callback(void *buf, mbedtls_x509_crt *crt, int depth, uint32_t *flags) +int esp_crt_verify_callback(void *buf, mbedtls_x509_crt* const crt, const int depth, uint32_t* const flags) { - mbedtls_x509_crt *child = crt; + const mbedtls_x509_crt* const child = crt; /* It's OK for a trusted cert to have a weak signature hash alg. as we already trust this certificate */ @@ -94,120 +226,119 @@ int esp_crt_verify_callback(void *buf, mbedtls_x509_crt *crt, int depth, uint32_ } - if (s_crt_bundle.crts == NULL) { + if (unlikely(s_crt_bundle == NULL)) { ESP_LOGE(TAG, "No certificates in bundle"); return MBEDTLS_ERR_X509_FATAL_ERROR; } - ESP_LOGD(TAG, "%d certificates in bundle", s_crt_bundle.num_certs); + ESP_LOGD(TAG, "%" PRIu16 " certificates in bundle", (uint16_t)esp_crt_get_certcount(s_crt_bundle)); - size_t name_len = 0; - const uint8_t *crt_name; + cert_t cert = esp_crt_find_cert(child->issuer_raw.p, child->issuer_raw.len); - bool crt_found = false; - int start = 0; - int end = s_crt_bundle.num_certs - 1; - int middle = (end - start) / 2; + if (likely(cert != NULL)) { - /* Look for the certificate using binary search on subject name */ - while (start <= end) { - name_len = s_crt_bundle.crts[middle][0] << 8 | s_crt_bundle.crts[middle][1]; - crt_name = s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET; + const int ret = esp_crt_check_signature(child, esp_crt_get_key(cert), esp_crt_get_key_len(cert)); - int cmp_res = memcmp(child->issuer_raw.p, crt_name, name_len ); - if (cmp_res == 0) { - crt_found = true; - break; - } else if (cmp_res < 0) { - end = middle - 1; + if (likely(ret == 0)) { + ESP_LOGI(TAG, "Certificate validated"); + *flags = 0; + return 0; } else { - start = middle + 1; + ESP_LOGE(TAG, "Certificate matched but signature verification failed"); +#if (CONFIG_LOG_DEFAULT_LEVEL_DEBUG || CONFIG_LOG_DEFAULT_LEVEL_VERBOSE) + char *cert_name = malloc((esp_crt_get_name_len(cert) + 1) * sizeof(char)); + if (cert_name) { + memcpy(cert_name, esp_crt_get_name(cert), esp_crt_get_name_len(cert)); + cert_name[esp_crt_get_name_len(cert)] = '\0'; + ESP_LOGE(TAG, "Certificate matched with %s but signature verification failed", cert_name); + free(cert_name); + } +#endif } - middle = (start + end) / 2; - } - int ret = MBEDTLS_ERR_X509_FATAL_ERROR; - if (crt_found) { - size_t key_len = s_crt_bundle.crts[middle][2] << 8 | s_crt_bundle.crts[middle][3]; - ret = esp_crt_check_signature(child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len, key_len); - } - - if (ret == 0) { - ESP_LOGI(TAG, "Certificate validated"); - *flags = 0; - return 0; + } else { + ESP_LOGI(TAG, "No matching trusted root certificate found"); } ESP_LOGE(TAG, "Failed to verify certificate"); - return MBEDTLS_ERR_X509_FATAL_ERROR; + return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; } +/** + * @brief Perform some consistency checks on the user-provided bundle data to try and make sure + * it actually is a certificate bundle. + * + * @param x509_bundle pointer to the bundle data + * @param bundle_size size of bundle data + * @return true the given bundle data is consistent + * @return false the given bundle data is invalid + */ +static bool esp_crt_check_bundle(const uint8_t* const x509_bundle, const size_t bundle_size) +{ + if (unlikely(x509_bundle == NULL || bundle_size <= (sizeof(uint32_t) + CRT_HEADER_SIZE))) { + // Bundle is too small for even one offset and one certificate + return false; + } -/* Initialize the bundle into an array so we can do binary search for certs, + // Pointer to the first offset entry + const uint32_t* offsets = (const uint32_t*)x509_bundle; + + if (unlikely(offsets[0] == 0 || (offsets[0] % sizeof(uint32_t)) != 0)) { + // First offset is invalid. + // The first certificate must start after N uint32_t offset values. + return false; + } + + if (unlikely(offsets[0] >= bundle_size)) { + // First cert starts beyond end of bundle + return false; + } + + const uint32_t num_certs = esp_crt_get_certcount(x509_bundle); + + if (unlikely(num_certs > CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS)) { + ESP_LOGE(TAG, "Cert bundle certificates exceed max allowed certificates"); + return false; + } + + // Check all offsets for consistency with certificate data + for (uint32_t i = 0; i < num_certs - 1; ++i) { + const uint32_t off = offsets[i]; + cert_t cert = x509_bundle + off; + // The next offset in the list must point to right after the current cert + const uint32_t expected_next_offset = off + esp_crt_get_len(cert); + + if (unlikely(offsets[i + 1] != expected_next_offset || expected_next_offset >= bundle_size)) { + return false; + } + } + + // All checks passed. + return true; +} + +/* the bundle generated by the python utility is already presorted by subject name */ -static esp_err_t esp_crt_bundle_init(const uint8_t *x509_bundle, size_t bundle_size) +static esp_err_t esp_crt_bundle_init(const uint8_t* const x509_bundle, const size_t bundle_size) { - if (bundle_size < BUNDLE_HEADER_OFFSET + CRT_HEADER_OFFSET) { - ESP_LOGE(TAG, "Invalid certificate bundle"); + if (likely(esp_crt_check_bundle(x509_bundle, bundle_size))) { + s_crt_bundle = x509_bundle; + return ESP_OK; + } else { return ESP_ERR_INVALID_ARG; } - - uint16_t num_certs = (x509_bundle[0] << 8) | x509_bundle[1]; - if (num_certs > CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS) { - ESP_LOGE(TAG, "No. of certs in the certificate bundle = %d exceeds\n" - "Max allowed certificates in the certificate bundle = %d\n" - "Please update the menuconfig option with appropriate value", num_certs, CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS); - return ESP_ERR_INVALID_ARG; - } - - const uint8_t **crts = calloc(num_certs, sizeof(x509_bundle)); - if (crts == NULL) { - ESP_LOGE(TAG, "Unable to allocate memory for bundle"); - return ESP_ERR_NO_MEM; - } - - const uint8_t *cur_crt; - /* This is the maximum region that is allowed to access */ - const uint8_t *bundle_end = x509_bundle + bundle_size; - cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET; - - for (int i = 0; i < num_certs; i++) { - crts[i] = cur_crt; - if (cur_crt + CRT_HEADER_OFFSET > bundle_end) { - ESP_LOGE(TAG, "Invalid certificate bundle"); - free(crts); - return ESP_ERR_INVALID_ARG; - } - size_t name_len = cur_crt[0] << 8 | cur_crt[1]; - size_t key_len = cur_crt[2] << 8 | cur_crt[3]; - cur_crt = cur_crt + CRT_HEADER_OFFSET + name_len + key_len; - } - - if (cur_crt > bundle_end) { - ESP_LOGE(TAG, "Invalid certificate bundle"); - free(crts); - return ESP_ERR_INVALID_ARG; - } - - /* The previous crt bundle is only updated when initialization of the - * current crt_bundle is successful */ - /* Free previous crt_bundle */ - free(s_crt_bundle.crts); - s_crt_bundle.num_certs = num_certs; - s_crt_bundle.crts = crts; - return ESP_OK; } esp_err_t esp_crt_bundle_attach(void *conf) { esp_err_t ret = ESP_OK; // If no bundle has been set by the user then use the bundle embedded in the binary - if (s_crt_bundle.crts == NULL) { + if (s_crt_bundle == NULL) { ret = esp_crt_bundle_init(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start); } - if (ret != ESP_OK) { + if (unlikely(ret != ESP_OK)) { ESP_LOGE(TAG, "Failed to attach bundle"); return ret; } @@ -218,8 +349,7 @@ 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_x509_crt_init(&s_dummy_crt); - mbedtls_ssl_conf_ca_chain(ssl_conf, &s_dummy_crt, NULL); + 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); } @@ -228,8 +358,7 @@ esp_err_t esp_crt_bundle_attach(void *conf) void esp_crt_bundle_detach(mbedtls_ssl_config *conf) { - free(s_crt_bundle.crts); - s_crt_bundle.crts = NULL; + s_crt_bundle = NULL; if (conf) { mbedtls_ssl_conf_verify(conf, NULL, NULL); } diff --git a/components/mbedtls/esp_crt_bundle/gen_crt_bundle.py b/components/mbedtls/esp_crt_bundle/gen_crt_bundle.py index 0211514a46..2e5bca544b 100755 --- a/components/mbedtls/esp_crt_bundle/gen_crt_bundle.py +++ b/components/mbedtls/esp_crt_bundle/gen_crt_bundle.py @@ -8,18 +8,16 @@ # The bundle will have the format: number of certificates; crt 1 subject name length; crt 1 public key length; # crt 1 subject name; crt 1 public key; crt 2... # -# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 - -from __future__ import with_statement - import argparse import csv import os import re import struct import sys -from io import open + +DEFAULT_CERT_BUNDLE_MAX_CERTS = 200 try: from cryptography import x509 @@ -54,9 +52,6 @@ class CertificateBundle: self.certificates = [] self.compressed_crts = [] - if os.path.isfile(ca_bundle_bin_file): - os.remove(ca_bundle_bin_file) - def add_from_path(self, crts_path): found = False @@ -116,11 +111,21 @@ class CertificateBundle: self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend())) status('Successfully added 1 certificate') - def create_bundle(self): + def create_bundle(self, max_certs=DEFAULT_CERT_BUNDLE_MAX_CERTS): + if max_certs < len(self.certificates): + critical(f'No. of certs in the certificate bundle = {len(self.certificates)} exceeds\n \ + Max allowed certificates in the certificate bundle = {max_certs} \ + Please update the menuconfig option with appropriate value') + raise ValueError + # Sort certificates in order to do binary search when looking up certificates self.certificates = sorted(self.certificates, key=lambda cert: cert.subject.public_bytes(default_backend())) - bundle = struct.pack('>H', len(self.certificates)) + # List of offsets in bytes from the start of the bundle to each certificate inside + offsets = [] + len_offsets = 4 * len(self.certificates) # final size of the offsets list + + bundle = b'' for crt in self.certificates: """ Read the public key as DER format """ @@ -132,12 +137,18 @@ class CertificateBundle: name_len = len(sub_name_der) key_len = len(pub_key_der) - len_data = struct.pack('>HH', name_len, key_len) + len_data = struct.pack(' #include "esp_err.h" #include "esp_log.h" @@ -41,6 +42,7 @@ extern const uint8_t server_cert_chain_pem_end[] asm("_binary_server_cert_chai extern const uint8_t server_pk_start[] asm("_binary_prvtkey_pem_start"); extern const uint8_t server_pk_end[] asm("_binary_prvtkey_pem_end"); +// `server_cert_bundle_corrupt` is created by generating the cert bundle using `server_root.pem` extern const uint8_t server_cert_bundle_start[] asm("_binary_server_cert_bundle_start"); extern const uint8_t server_cert_bundle_end[] asm("_binary_server_cert_bundle_end"); @@ -272,7 +274,7 @@ void client_task(void *pvParameters) goto exit; } - /* Test with default crt bundle that doesnt contain the ca crt */ + /* Test with default crt bundle that does not contain the ca crt */ ESP_LOGI(TAG, "Connecting to %s:%s...", SERVER_ADDRESS, SERVER_PORT); if ((ret = mbedtls_net_connect(&client.client_fd, SERVER_ADDRESS, SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) { ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); @@ -300,7 +302,7 @@ void client_task(void *pvParameters) } else { ESP_LOGE(TAG, "Certificate verification failed!"); } - TEST_ASSERT(res == ESP_CRT_VALIDATE_FAIL); + TEST_ASSERT_EQUAL(ESP_CRT_VALIDATE_FAIL, res); // Reset session before new connection mbedtls_ssl_close_notify(&client.ssl); @@ -338,7 +340,7 @@ void client_task(void *pvParameters) } else { ESP_LOGE(TAG, "Certificate verification failed!"); } - TEST_ASSERT(res == ESP_CRT_VALIDATE_OK); + TEST_ASSERT_EQUAL(ESP_CRT_VALIDATE_OK, res); // Reset session before new connection mbedtls_ssl_close_notify(&client.ssl); @@ -406,7 +408,7 @@ TEST_CASE("custom certificate bundle - weak hash", "[mbedtls]") mbedtls_x509_crt_init( &crt ); mbedtls_x509_crt_parse(&crt, bad_md_crt_pem_start, bad_md_crt_pem_end - bad_md_crt_pem_start); - TEST_ASSERT(mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL) == 0); + TEST_ASSERT_EQUAL(0, mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL)); mbedtls_x509_crt_free(&crt); @@ -426,62 +428,119 @@ TEST_CASE("custom certificate bundle - wrong signature", "[mbedtls]") /* esp32.com cert chain where 1 byte in the signature is changed */ printf("Testing certificate with wrong signature\n"); mbedtls_x509_crt_parse(&crt, wrong_sig_crt_pem_start, wrong_sig_crt_pem_end - wrong_sig_crt_pem_start); - TEST_ASSERT(mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL) != 0); + TEST_ASSERT_NOT_EQUAL(0, mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL)); mbedtls_x509_crt_free(&crt); mbedtls_x509_crt_init( &crt ); /* the correct esp32.com cert chain*/ printf("Testing certificate with correct signature\n"); mbedtls_x509_crt_parse(&crt, correct_sig_crt_pem_start, correct_sig_crt_pem_end - correct_sig_crt_pem_start); - TEST_ASSERT(mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL) == 0); + TEST_ASSERT_EQUAL(0, mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL)); mbedtls_x509_crt_free(&crt); esp_crt_bundle_detach(NULL); } -TEST_CASE("custom certificate bundle init API - bound checking", "[mbedtls]") +TEST_CASE("custom certificate bundle init API - bound checking - NULL certificate bundle", "[mbedtls]") { - - uint8_t test_bundle[256] = {0}; esp_err_t esp_ret; + + /* The API should fail when NULL is passed as the bundle */ + esp_ret = esp_crt_bundle_set(NULL, 0); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); +} + +TEST_CASE("custom certificate bundle init API - bound checking - Invalid size of certificate bundle", "[mbedtls]") +{ + uint8_t test_bundle[1024] = {0}; + esp_err_t esp_ret; + /* The API should fail with bundle size given as 1 */ esp_ret = esp_crt_bundle_set(test_bundle, 1); - TEST_ASSERT( esp_ret == ESP_ERR_INVALID_ARG); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); +} + +TEST_CASE("custom certificate bundle init API - bound checking - Invalid first certificate offset", "[mbedtls]") +{ + uint8_t test_bundle[1024] = {0}; + esp_err_t esp_ret; + + /* Check that the esp_crt_bundle_set API will not accept + * the first offset to be invalid */ + + /* The first certificate must start after N uint32_t offset values, + * thus, it cannot start from the 0th position */ + test_bundle[0] = 0; + esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); + + /* The first certificate must start after N uint32_t offset values, thus, + * the offset from where the it would start should be divisible by sizeof(uint32_t) */ + test_bundle[0] = 1; + esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); + + /* Check that the esp_crt_bundle_set API will not accept a bundle + * which in which the first cert starts beyond end of bundle*/ + uint8_t *dummy_test_bundle = test_bundle + sizeof(uint32_t); + + esp_ret = esp_crt_bundle_set(dummy_test_bundle, sizeof(test_bundle)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); +} + +TEST_CASE("custom certificate bundle init API - bound checking - Certificates count overflow", "[mbedtls]") +{ + uint8_t test_bundle[1024] = {0}; + esp_err_t esp_ret; + + memset(test_bundle, 0, sizeof(test_bundle)); /* Check that the esp_crt_bundle_set API will not accept a bundle * which has more no. of certs than configured in * CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS */ - - uint8_t rand; - esp_fill_random(&rand, 1); - test_bundle[0] = rand; - - /* Make sure that the number of certs will always be greater than - * CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS */ - test_bundle[1] = rand + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS; + *((uint32_t*) test_bundle) = ((CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS + 1) * sizeof(uint32_t)); esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); - TEST_ASSERT( esp_ret == ESP_ERR_INVALID_ARG); - - /* The API should fail with bundle_size < BUNDLE_HEADER_OFFSET (2) + CRT_HEADER_OFFSET (4) */ - test_bundle[0] = 0; - test_bundle[1] = 1; /* set num_certs = 1 */ - esp_ret = esp_crt_bundle_set(test_bundle, 5); - TEST_ASSERT(esp_ret == ESP_ERR_INVALID_ARG); - - /* Cert number is greater than actual certs present, The API should fail */ - /* Actual No. of certs present in bundle = 1, setting num_certs to 5 */ - test_bundle[1] = 5; /* num_certs */ - test_bundle[3] = 5; /* cert_1_name_len */ - test_bundle[5] = 10; /* cert_1_pub_key_len */ - /* Actual bundle size becomes BUNDLE_HEADER_OFFSET (2) + CRT_HEADER_OFFSET (4) + cert_1_name_len(5) + cert_1_pub_key_len(10) - * i.e. 21 bytes */ - esp_ret = esp_crt_bundle_set(test_bundle, 21); - TEST_ASSERT(esp_ret == ESP_ERR_INVALID_ARG); - - /* The API should fail if bundle_size < BUNDLE_HEADER_OFFSET (2) + CRT_HEADER_OFFSET (4) + cert_1_name_len(5) + cert_1_pub_key_len(10) */ - esp_ret = esp_crt_bundle_set(test_bundle, 20); - TEST_ASSERT(esp_ret == ESP_ERR_INVALID_ARG); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); esp_crt_bundle_detach(NULL); } + +TEST_CASE("custom certificate bundle init API - bound checking - Incorrect certificate offset", "[mbedtls]") +{ + uint8_t test_bundle[1024] = {0}; + esp_err_t esp_ret; + + memset(test_bundle, 0, sizeof(test_bundle)); + + /* Check that the esp_crt_bundle_set API will not accept a bundle where + all offsets are not consistent with certificate data */ + + /* + | offset 1 | offset 2 | Cert 1 name len | Cert 1 key len | Cert 1 name | Cert 1 key | Cert 2 name len | ..... | + | ----- offsets ----- | + | ---------------------- Certificate 1 ---------------------- | + | ---- Certificate 2 ---- | + */ + + *((uint32_t*) &test_bundle[0]) = (2 * sizeof(uint32_t)); + *((uint16_t*) &test_bundle[8]) = 2; // Cert 1 name len + *((uint16_t*) &test_bundle[10]) = 4; // Cert 1 key len + + /* Correct offset of certificate 2 should be + = 2 * sizeof(uint32_t) (Offsets of 2 certs) + 2 * sizeof(uint16_t) (Cert name and len) + 2 (Cert 1 name len) + 4 (Cert 1 key len); + = 18 + */ + *((uint32_t*) &test_bundle[4]) = 16; // Incorrect certificate 2 offset + + esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); + + /* Check that the esp_crt_bundle_set API will not accept a bundle where + all offsets are not consistency with certificate data and the certificate + offsets exceeds the bundle size */ + *((uint32_t*) &test_bundle[4]) = sizeof(test_bundle) + 1; // Offset exceeds the test_bundle size + + esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); +} diff --git a/examples/protocols/https_x509_bundle/main/https_x509_bundle_example_main.c b/examples/protocols/https_x509_bundle/main/https_x509_bundle_example_main.c index 966aa8173e..33e3040cf9 100644 --- a/examples/protocols/https_x509_bundle/main/https_x509_bundle_example_main.c +++ b/examples/protocols/https_x509_bundle/main/https_x509_bundle_example_main.c @@ -43,11 +43,24 @@ #include "esp_tls.h" #include "esp_crt_bundle.h" +#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL +#define MAX_URLS 9 +#else #define MAX_URLS 2 +#endif static const char *web_urls[MAX_URLS] = { "https://www.howsmyssl.com/a/check", "https://espressif.com", +#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL + "https://letsencrypt.org", + "https://www.identrust.com", + "https://www.globalsign.com", + "https://www.sectigo.com", + "https://www.digicert.com", + "https://www.godaddy.com", + "https://rainmaker.espressif.com", // Amazon +#endif }; static const char *TAG = "example"; diff --git a/examples/protocols/https_x509_bundle/pytest_https_x509_bundle.py b/examples/protocols/https_x509_bundle/pytest_https_x509_bundle.py index 12ef0d1f1d..2e6aa59810 100644 --- a/examples/protocols/https_x509_bundle/pytest_https_x509_bundle.py +++ b/examples/protocols/https_x509_bundle/pytest_https_x509_bundle.py @@ -1,10 +1,9 @@ -# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import logging import os import pytest -from common_test_methods import get_env_config_variable from pytest_embedded import Dut @@ -21,17 +20,11 @@ def test_examples_protocol_https_x509_bundle(dut: Dut) -> None: binary_file = os.path.join(dut.app.binary_path, 'https_x509_bundle.bin') bin_size = os.path.getsize(binary_file) logging.info('https_x509_bundle_bin_size : {}KB'.format(bin_size // 1024)) - # Connect to AP - if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: - dut.expect('Please input ssid password:') - env_name = 'wifi_ap' - ap_ssid = get_env_config_variable(env_name, 'ap_ssid') - ap_password = get_env_config_variable(env_name, 'ap_password') - dut.write(f'{ap_ssid} {ap_password}') dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30) # start test num_URLS = int(dut.expect(r'Connecting to (\d+) URLs', timeout=30)[1].decode()) - dut.expect(r'Connection established to ([\s\S]*)', timeout=30) + for _ in range(num_URLS): + dut.expect(r'Connection established to ([\s\S]*)', timeout=30) dut.expect('Completed {} connections'.format(num_URLS), timeout=60) @@ -44,15 +37,24 @@ def test_examples_protocol_https_x509_bundle_dynamic_buffer(dut: Dut) -> None: binary_file = os.path.join(dut.app.binary_path, 'https_x509_bundle.bin') bin_size = os.path.getsize(binary_file) logging.info('https_x509_bundle_bin_size : {}KB'.format(bin_size // 1024)) - # Connect to AP - if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: - dut.expect('Please input ssid password:') - env_name = 'wifi_ap' - ap_ssid = get_env_config_variable(env_name, 'ap_ssid') - ap_password = get_env_config_variable(env_name, 'ap_password') - dut.write(f'{ap_ssid} {ap_password}') dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30) # start test num_URLS = int(dut.expect(r'Connecting to (\d+) URLs', timeout=30)[1].decode()) dut.expect(r'Connection established to ([\s\S]*)', timeout=30) dut.expect('Completed {} connections'.format(num_URLS), timeout=60) + + +@pytest.mark.esp32 +@pytest.mark.ethernet +@pytest.mark.parametrize('config', ['default_crt_bundle',], indirect=True) +def test_examples_protocol_https_x509_bundle_default_crt_bundle_stress_test(dut: Dut) -> None: + # check and log bin size + binary_file = os.path.join(dut.app.binary_path, 'https_x509_bundle.bin') + bin_size = os.path.getsize(binary_file) + logging.info('https_x509_bundle_bin_size : {}KB'.format(bin_size // 1024)) + dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30) + # start test + num_URLS = int(dut.expect(r'Connecting to (\d+) URLs', timeout=30)[1].decode()) + for _ in range(num_URLS): + dut.expect(r'Connection established to ([\s\S]*)', timeout=30) + dut.expect('Completed {} connections'.format(num_URLS), timeout=60) diff --git a/examples/protocols/https_x509_bundle/sdkconfig.ci.default_crt_bundle b/examples/protocols/https_x509_bundle/sdkconfig.ci.default_crt_bundle new file mode 100644 index 0000000000..3f3ad687e4 --- /dev/null +++ b/examples/protocols/https_x509_bundle/sdkconfig.ci.default_crt_bundle @@ -0,0 +1,10 @@ +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y + +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1