Merge branch 'contrib/github_pr_13204' into 'master'

Reduce RAM usage by modifying the certificate bundle format (GitHub PR)

Closes IDFGH-12148

See merge request espressif/esp-idf!29595
This commit is contained in:
Mahavir Jain
2024-10-11 05:05:43 +08:00
10 changed files with 414 additions and 188 deletions

View File

@@ -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}

View File

@@ -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 <string.h>
#include <stdbool.h>
#include <sys/param.h>
#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 //<! offset of certificate name length value
#define CRT_KEY_LEN_OFFSET (CRT_NAME_LEN_OFFSET + sizeof(uint16_t)) //<! offset of certificate key length value
#define CRT_NAME_OFFSET (CRT_KEY_LEN_OFFSET + sizeof(uint16_t)) //<! certificate name data starts here
#define CRT_HEADER_SIZE CRT_NAME_OFFSET //<! size of certificate header
static const char *TAG = "esp-x509-crt-bundle";
@@ -17,63 +56,156 @@ static const char *TAG = "esp-x509-crt-bundle";
* cacert_ptr passes non-NULL check during handshake */
static mbedtls_x509_crt s_dummy_crt;
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");
typedef const uint8_t* bundle_t;
typedef const uint8_t* cert_t;
typedef struct crt_bundle_t {
const uint8_t **crts;
uint16_t num_certs;
size_t x509_crt_bundle_len;
} crt_bundle_t;
static bundle_t s_crt_bundle;
static crt_bundle_t s_crt_bundle;
// Read a 16-bit value stored in little-endian format from the given address
static uint16_t get16_le(const uint8_t* ptr)
{
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
return *((const uint16_t*)ptr);
#else
return (((uint16_t)ptr[1]) << 8) | ptr[0];
#endif
}
static int esp_crt_check_signature(mbedtls_x509_crt *child, const uint8_t *pub_key_buf, size_t pub_key_len);
static uint16_t esp_crt_get_name_len(const cert_t cert)
{
return get16_le(cert + CRT_NAME_LEN_OFFSET);
}
static const uint8_t* esp_crt_get_name(const cert_t cert)
{
return cert + CRT_NAME_OFFSET;
}
static int esp_crt_check_signature(mbedtls_x509_crt *child, const uint8_t *pub_key_buf, size_t pub_key_len)
static uint16_t esp_crt_get_key_len(const cert_t cert)
{
return get16_le(cert + CRT_KEY_LEN_OFFSET);
}
static const uint8_t* esp_crt_get_key(const cert_t cert)
{
return esp_crt_get_name(cert) + esp_crt_get_name_len(cert);
}
static uint16_t esp_crt_get_len(const cert_t cert)
{
return CRT_HEADER_SIZE + esp_crt_get_name_len(cert) + esp_crt_get_key_len(cert);
}
static uint32_t esp_crt_get_cert_offset(const bundle_t bundle, const uint32_t index)
{
return ((const uint32_t*)bundle)[index];
}
static uint32_t esp_crt_get_certcount(const bundle_t bundle)
{
// Offset of 1st certificate == end of offset list == size of offset list == # of certs * sizeof(uint32_t)
return esp_crt_get_cert_offset(bundle, 0) / sizeof(uint32_t);
}
/**
* @brief Get the certificate at the given index within a bundle.
*
* @param bundle pointer to the \c bundle_t
* @param index of the certificate; must be less than \c esp_crt_get_certcount(...) !
* @return pointer to the certificate
*/
static cert_t esp_crt_get_cert(const bundle_t bundle, const uint32_t index)
{
return bundle + esp_crt_get_cert_offset(bundle, index);
}
static int esp_crt_check_signature(const mbedtls_x509_crt* child, const uint8_t* pub_key_buf, const size_t pub_key_len)
{
int ret = 0;
mbedtls_x509_crt parent;
mbedtls_pk_context pubkey;
const mbedtls_md_info_t *md_info;
unsigned char hash[MBEDTLS_MD_MAX_SIZE];
mbedtls_x509_crt_init(&parent);
mbedtls_pk_init(&pubkey);
if ( (ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf, pub_key_len) ) != 0) {
ESP_LOGE(TAG, "PK parse failed with error %X", ret);
if (unlikely((ret = mbedtls_pk_parse_public_key(&pubkey, pub_key_buf, pub_key_len)) != 0)) {
ESP_LOGE(TAG, "PK parse failed with error 0x%x", -ret);
goto cleanup;
}
// Fast check to avoid expensive computations when not necessary
if (!mbedtls_pk_can_do(&parent.pk, child->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);
}

View File

@@ -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('<HH', name_len, key_len)
# Certificate starts at this position in the bundle
offsets.append(len_offsets + len(bundle))
bundle += len_data
bundle += sub_name_der
bundle += pub_key_der
# Output all offsets before the first certificate
bundle = struct.pack('<{0:d}L'.format(len(offsets)), *offsets) + bundle
return bundle
def add_with_filter(self, crts_path, filter_path):
@@ -182,6 +193,8 @@ def main():
help='Paths to the custom certificate folders or files to parse, parses all .pem or .der files')
parser.add_argument('--filter', '-f', help='Path to CSV-file where the second columns contains the name of the certificates \
that should be included from cacrt_all.pem')
parser.add_argument('--max-certs', '-m', help='Maximum number of certificates allowed in the certificate bundle',
type=int, default=DEFAULT_CERT_BUNDLE_MAX_CERTS)
args = parser.parse_args()
@@ -202,7 +215,7 @@ def main():
status('Successfully added %d certificates in total' % len(bundle.certificates))
crt_bundle = bundle.create_bundle()
crt_bundle = bundle.create_bundle(args.max_certs)
with open(ca_bundle_bin_file, 'wb') as f:
f.write(crt_bundle)

View File

@@ -6,8 +6,9 @@
*
* SPDX-License-Identifier: Apache-2.0
*
* SPDX-FileContributor: 2019-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileContributor: 2019-2024 Espressif Systems (Shanghai) CO LTD
*/
#include <string.h>
#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);
}

View File

@@ -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";

View File

@@ -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)

View File

@@ -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