forked from espressif/esp-idf
feat(mbedtls/esp_crt_bundle): Reduced RAM & stack use of cert bundle
Closes https://github.com/espressif/esp-idf/pull/13204 Signed-off-by: harshal.patil <harshal.patil@espressif.com>
This commit is contained in:
@@ -61,7 +61,7 @@ if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)
|
|||||||
list(APPEND crt_paths ${custom_bundle_path})
|
list(APPEND crt_paths ${custom_bundle_path})
|
||||||
|
|
||||||
endif()
|
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
|
get_filename_component(crt_bundle
|
||||||
${bundle_name}
|
${bundle_name}
|
||||||
|
@@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
|
||||||
|
#include "esp_check.h"
|
||||||
#include "esp_crt_bundle.h"
|
#include "esp_crt_bundle.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
#define BUNDLE_HEADER_OFFSET 2
|
#include "mbedtls/pk.h"
|
||||||
#define CRT_HEADER_OFFSET 4
|
#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";
|
static const char *TAG = "esp-x509-crt-bundle";
|
||||||
|
|
||||||
@@ -17,63 +56,147 @@ static const char *TAG = "esp-x509-crt-bundle";
|
|||||||
* cacert_ptr passes non-NULL check during handshake */
|
* cacert_ptr passes non-NULL check during handshake */
|
||||||
static mbedtls_x509_crt s_dummy_crt;
|
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_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");
|
||||||
|
|
||||||
|
typedef const uint8_t* bundle_t;
|
||||||
|
typedef const uint8_t* cert_t;
|
||||||
|
|
||||||
typedef struct crt_bundle_t {
|
static bundle_t s_crt_bundle;
|
||||||
const uint8_t **crts;
|
|
||||||
uint16_t num_certs;
|
|
||||||
size_t x509_crt_bundle_len;
|
|
||||||
} crt_bundle_t;
|
|
||||||
|
|
||||||
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;
|
int ret = 0;
|
||||||
mbedtls_x509_crt parent;
|
mbedtls_pk_context pubkey;
|
||||||
const mbedtls_md_info_t *md_info;
|
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) {
|
if ( unlikely( (ret = mbedtls_pk_parse_public_key(&pubkey, pub_key_buf, pub_key_len)) != 0 ) ) {
|
||||||
ESP_LOGE(TAG, "PK parse failed with error %X", ret);
|
ESP_LOGE(TAG, "PK parse failed with error 0x%x", -ret);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Fast check to avoid expensive computations when not necessary
|
// Fast check to avoid expensive computations when not necessary
|
||||||
if (!mbedtls_pk_can_do(&parent.pk, child->MBEDTLS_PRIVATE(sig_pk))) {
|
if ( unlikely( !mbedtls_pk_can_do( &pubkey, child->MBEDTLS_PRIVATE(sig_pk)) ) ) {
|
||||||
ESP_LOGE(TAG, "Simple compare failed");
|
ESP_LOGE(TAG, "Unsuitable public key");
|
||||||
ret = -1;
|
ret = MBEDTLS_ERR_PK_TYPE_MISMATCH;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
md_info = mbedtls_md_info_from_type(child->MBEDTLS_PRIVATE(sig_md));
|
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;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( (ret = mbedtls_pk_verify_ext( child->MBEDTLS_PRIVATE(sig_pk), child->MBEDTLS_PRIVATE(sig_opts), &parent.pk,
|
unsigned char hash[MBEDTLS_MD_MAX_SIZE];
|
||||||
child->MBEDTLS_PRIVATE(sig_md), hash, mbedtls_md_get_size( md_info ),
|
const unsigned char md_size = mbedtls_md_get_size(md_info);
|
||||||
child->MBEDTLS_PRIVATE(sig).p, child->MBEDTLS_PRIVATE(sig).len )) != 0 ) {
|
|
||||||
|
|
||||||
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;
|
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:
|
cleanup:
|
||||||
mbedtls_x509_crt_free(&parent);
|
mbedtls_pk_free(&pubkey);
|
||||||
|
|
||||||
return ret;
|
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
|
/* This callback is called for every certificate in the chain. If the chain
|
||||||
* is proper each intermediate certificate is validated through its parent
|
* is proper each intermediate certificate is validated through its parent
|
||||||
@@ -81,9 +204,9 @@ cleanup:
|
|||||||
* only verify the first untrusted link in the chain is signed by the
|
* only verify the first untrusted link in the chain is signed by the
|
||||||
* root certificate in the trusted bundle
|
* 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.
|
/* It's OK for a trusted cert to have a weak signature hash alg.
|
||||||
as we already trust this certificate */
|
as we already trust this certificate */
|
||||||
@@ -93,121 +216,119 @@ int esp_crt_verify_callback(void *buf, mbedtls_x509_crt *crt, int depth, uint32_
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( unlikely( s_crt_bundle == NULL ) ) {
|
||||||
if (s_crt_bundle.crts == NULL) {
|
|
||||||
ESP_LOGE(TAG, "No certificates in bundle");
|
ESP_LOGE(TAG, "No certificates in bundle");
|
||||||
return MBEDTLS_ERR_X509_FATAL_ERROR;
|
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;
|
cert_t cert = esp_crt_find_cert(child->issuer_raw.p, child->issuer_raw.len);
|
||||||
const uint8_t *crt_name;
|
|
||||||
|
|
||||||
bool crt_found = false;
|
if ( likely( cert != NULL ) ) {
|
||||||
int start = 0;
|
|
||||||
int end = s_crt_bundle.num_certs - 1;
|
|
||||||
int middle = (end - start) / 2;
|
|
||||||
|
|
||||||
/* Look for the certificate using binary search on subject name */
|
const int ret = esp_crt_check_signature(child, esp_crt_get_key(cert), esp_crt_get_key_len(cert));
|
||||||
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;
|
|
||||||
|
|
||||||
int cmp_res = memcmp(child->issuer_raw.p, crt_name, name_len );
|
if ( likely( ret == 0 ) ) {
|
||||||
if (cmp_res == 0) {
|
ESP_LOGI(TAG, "Certificate validated");
|
||||||
crt_found = true;
|
*flags = 0;
|
||||||
break;
|
return 0;
|
||||||
} else if (cmp_res < 0) {
|
|
||||||
end = middle - 1;
|
|
||||||
} else {
|
} 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;
|
} else {
|
||||||
if (crt_found) {
|
ESP_LOGI(TAG, "No matching trusted root certificate 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGE(TAG, "Failed to verify certificate");
|
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
|
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) {
|
if ( likely( esp_crt_check_bundle(x509_bundle, bundle_size) ) ) {
|
||||||
ESP_LOGE(TAG, "Invalid certificate bundle");
|
s_crt_bundle = x509_bundle;
|
||||||
|
return ESP_OK;
|
||||||
|
} else {
|
||||||
return ESP_ERR_INVALID_ARG;
|
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 esp_crt_bundle_attach(void *conf)
|
||||||
{
|
{
|
||||||
esp_err_t ret = ESP_OK;
|
esp_err_t ret = ESP_OK;
|
||||||
// If no bundle has been set by the user then use the bundle embedded in the binary
|
// 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);
|
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");
|
ESP_LOGE(TAG, "Failed to attach bundle");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -218,8 +339,7 @@ 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_x509_crt_init(&s_dummy_crt);
|
mbedtls_ssl_conf_ca_chain(ssl_conf, (mbedtls_x509_crt*)&s_dummy_crt, NULL);
|
||||||
mbedtls_ssl_conf_ca_chain(ssl_conf, &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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +348,7 @@ esp_err_t esp_crt_bundle_attach(void *conf)
|
|||||||
|
|
||||||
void esp_crt_bundle_detach(mbedtls_ssl_config *conf)
|
void esp_crt_bundle_detach(mbedtls_ssl_config *conf)
|
||||||
{
|
{
|
||||||
free(s_crt_bundle.crts);
|
s_crt_bundle = NULL;
|
||||||
s_crt_bundle.crts = NULL;
|
|
||||||
if (conf) {
|
if (conf) {
|
||||||
mbedtls_ssl_conf_verify(conf, NULL, NULL);
|
mbedtls_ssl_conf_verify(conf, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
@@ -8,18 +8,16 @@
|
|||||||
# The bundle will have the format: number of certificates; crt 1 subject name length; crt 1 public key length;
|
# 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...
|
# 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
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
from io import open
|
|
||||||
|
DEFAULT_CERT_BUNDLE_MAX_CERTS = 200
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
@@ -54,9 +52,6 @@ class CertificateBundle:
|
|||||||
self.certificates = []
|
self.certificates = []
|
||||||
self.compressed_crts = []
|
self.compressed_crts = []
|
||||||
|
|
||||||
if os.path.isfile(ca_bundle_bin_file):
|
|
||||||
os.remove(ca_bundle_bin_file)
|
|
||||||
|
|
||||||
def add_from_path(self, crts_path):
|
def add_from_path(self, crts_path):
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
@@ -116,11 +111,21 @@ class CertificateBundle:
|
|||||||
self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend()))
|
self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend()))
|
||||||
status('Successfully added 1 certificate')
|
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
|
# 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()))
|
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:
|
for crt in self.certificates:
|
||||||
""" Read the public key as DER format """
|
""" Read the public key as DER format """
|
||||||
@@ -132,12 +137,18 @@ class CertificateBundle:
|
|||||||
|
|
||||||
name_len = len(sub_name_der)
|
name_len = len(sub_name_der)
|
||||||
key_len = len(pub_key_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 += len_data
|
||||||
bundle += sub_name_der
|
bundle += sub_name_der
|
||||||
bundle += pub_key_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
|
return bundle
|
||||||
|
|
||||||
def add_with_filter(self, crts_path, filter_path):
|
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')
|
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 \
|
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')
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -202,7 +215,7 @@ def main():
|
|||||||
|
|
||||||
status('Successfully added %d certificates in total' % len(bundle.certificates))
|
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:
|
with open(ca_bundle_bin_file, 'wb') as f:
|
||||||
f.write(crt_bundle)
|
f.write(crt_bundle)
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user