From 974a7d9bef7002b4ca6b1aba9ff9a920afcd270e Mon Sep 17 00:00:00 2001 From: esp32wrangler <44122875+esp32wrangler@users.noreply.github.com> Date: Wed, 5 Jan 2022 16:56:35 +0100 Subject: [PATCH 1/3] Add certificate bundle capability to WiFiClientSecure Enable usage of the ESP32 IDF's certificate bundle for WiFiClientSecure connections. Adds the ability to load a bundle or root certificates and use them for authenticating SSL servers. Based on work from Onno-Dirkzwager, Duckle29, kubo6472, meltdown03, kinafu and others. See also: - https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_crt_bundle.html - https://github.com/espressif/arduino-esp32/issues/3646 - libraries/WiFiClientSecure/README.md --- CMakeLists.txt | 1 + libraries/WiFiClientSecure/README.md | 26 ++ .../WiFiClientSecure/src/WiFiClientSecure.cpp | 17 +- .../WiFiClientSecure/src/WiFiClientSecure.h | 2 + .../WiFiClientSecure/src/esp_crt_bundle.c | 218 +++++++++++++++++ .../WiFiClientSecure/src/esp_crt_bundle.h | 68 ++++++ libraries/WiFiClientSecure/src/ssl_client.cpp | 12 +- libraries/WiFiClientSecure/src/ssl_client.h | 2 +- tools/gen_crt_bundle.py | 227 ++++++++++++++++++ 9 files changed, 568 insertions(+), 5 deletions(-) create mode 100644 libraries/WiFiClientSecure/src/esp_crt_bundle.c create mode 100644 libraries/WiFiClientSecure/src/esp_crt_bundle.h create mode 100644 tools/gen_crt_bundle.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 59d1ded8e80..a8f05c22756 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ set(LIBRARY_SRCS libraries/WebServer/src/Parsing.cpp libraries/WebServer/src/detail/mimetable.cpp libraries/WiFiClientSecure/src/ssl_client.cpp + libraries/WiFiClientSecure/src/esp_crt_bundle.cpp libraries/WiFiClientSecure/src/WiFiClientSecure.cpp libraries/WiFi/src/WiFiAP.cpp libraries/WiFi/src/WiFiClient.cpp diff --git a/libraries/WiFiClientSecure/README.md b/libraries/WiFiClientSecure/README.md index fd5f4f51938..aa5199d2a49 100644 --- a/libraries/WiFiClientSecure/README.md +++ b/libraries/WiFiClientSecure/README.md @@ -26,6 +26,32 @@ Then: Please see the WiFiClientSecure example. +Using a bundle of root certificate authority certificates +--------------------------------------------------------- +This method is similar to the single root certificate verfication above, but it uses a standard set of +root certificates from Mozilla to authenticate against, while the previous method only accepts a single +certificate for a given server. This allows the client to connect to all public SSL servers. + +To use this feature in PlatformIO: +1. create a certificate bundle as described in the document below, or obtain a pre-built one you trust: +https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_crt_bundle.htm +(gen_crt_bundle.py can be found in the /tools folder) + a. note: the full bundle will take up around 64k of flash space, but has minimal RAM usage, as only + the index of the certificates is kept in RAM +2. Place the bundle under the file name "data/cert/x509_crt_bundle.bin" in your platformio project +3. add "board_build.embed_files = data/cert/x509_crt_bundle.bin" in your platformio.ini +4. add the following global declaration in your project: + extern const uint8_t rootca_crt_bundle_start[] asm("_binary_data_cert_x509_crt_bundle_bin_start"); +5. before initiating the first SSL connection, call + my_client.setCACertBundle(rootca_crt_bundle_start); + +To use this feature in Android IDE: +If the Arduino IDE added support for embedding files in the meantime, then follow the instructions above. +If not, you have three choices: +1. convert your project to PlatformIO +2. create a makefile where you can add the idf_component_register() declaration to include the certificate bundle +3. Store the bundle as a SPIFFS file, but then you have to load it into RAM in runtime and waste 64k of precious memory + Using a root CA cert and client cert/keys ----------------------------------------- This method authenticates the server and additionally also authenticates diff --git a/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp b/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp index 4f19d57a4d0..7f6057c6d65 100644 --- a/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp +++ b/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp @@ -19,6 +19,7 @@ */ #include "WiFiClientSecure.h" +#include "esp_crt_bundle.h" #include #include #include @@ -44,6 +45,7 @@ WiFiClientSecure::WiFiClientSecure() _psKey = NULL; next = NULL; _alpn_protos = NULL; + _use_ca_bundle = false; } @@ -129,7 +131,7 @@ int WiFiClientSecure::connect(const char *host, uint16_t port, const char *CA_ce if(_timeout > 0){ sslclient->handshake_timeout = _timeout; } - int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos); + int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, _use_ca_bundle, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos); _lastError = ret; if (ret < 0) { log_e("start_ssl_client: %d", ret); @@ -149,7 +151,7 @@ int WiFiClientSecure::connect(const char *host, uint16_t port, const char *pskId if(_timeout > 0){ sslclient->handshake_timeout = _timeout; } - int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos); + int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, false, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos); _lastError = ret; if (ret < 0) { log_e("start_ssl_client: %d", ret); @@ -263,6 +265,17 @@ void WiFiClientSecure::setCACert (const char *rootCA) _CA_cert = rootCA; } + void WiFiClientSecure::setCACertBundle(const uint8_t * bundle) + { + if (bundle != NULL) + { + esp_crt_bundle_set(bundle); + _use_ca_bundle = true; + } else { + _use_ca_bundle = false; + } + } + void WiFiClientSecure::setCertificate (const char *client_ca) { _cert = client_ca; diff --git a/libraries/WiFiClientSecure/src/WiFiClientSecure.h b/libraries/WiFiClientSecure/src/WiFiClientSecure.h index bba94ceffbc..3a36b2fb9a8 100644 --- a/libraries/WiFiClientSecure/src/WiFiClientSecure.h +++ b/libraries/WiFiClientSecure/src/WiFiClientSecure.h @@ -40,6 +40,7 @@ class WiFiClientSecure : public WiFiClient const char *_pskIdent; // identity for PSK cipher suites const char *_psKey; // key in hex for PSK cipher suites const char **_alpn_protos; + bool _use_ca_bundle; public: WiFiClientSecure *next; @@ -70,6 +71,7 @@ class WiFiClientSecure : public WiFiClient void setCertificate(const char *client_ca); void setPrivateKey (const char *private_key); bool loadCACert(Stream& stream, size_t size); + void setCACertBundle(const uint8_t * bundle); bool loadCertificate(Stream& stream, size_t size); bool loadPrivateKey(Stream& stream, size_t size); bool verify(const char* fingerprint, const char* domain_name); diff --git a/libraries/WiFiClientSecure/src/esp_crt_bundle.c b/libraries/WiFiClientSecure/src/esp_crt_bundle.c new file mode 100644 index 00000000000..747f86239d5 --- /dev/null +++ b/libraries/WiFiClientSecure/src/esp_crt_bundle.c @@ -0,0 +1,218 @@ +// Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include +#include +#include +#include "esp_crt_bundle.h" +#include "esp_err.h" + +#define BUNDLE_HEADER_OFFSET 2 +#define CRT_HEADER_OFFSET 4 + +static const char *TAG = "esp-x509-crt-bundle"; + +/* a dummy certificate so that + * cacert_ptr passes non-NULL check during handshake */ +static mbedtls_x509_crt s_dummy_crt; + + +typedef struct crt_bundle_t { + const uint8_t **crts; + uint16_t num_certs; + size_t x509_crt_bundle_len; +} crt_bundle_t; + +static crt_bundle_t s_crt_bundle; + +static int esp_crt_verify_callback(void *buf, mbedtls_x509_crt *crt, int data, uint32_t *flags); +static int esp_crt_check_signature(mbedtls_x509_crt *child, const uint8_t *pub_key_buf, size_t pub_key_len); + + +static int esp_crt_check_signature(mbedtls_x509_crt *child, const uint8_t *pub_key_buf, size_t pub_key_len) +{ + int ret = 0; + mbedtls_x509_crt parent; + const mbedtls_md_info_t *md_info; + unsigned char hash[MBEDTLS_MD_MAX_SIZE]; + + mbedtls_x509_crt_init(&parent); + + if ( (ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf, pub_key_len) ) != 0) { + log_e("PK parse failed with error %X", ret); + goto cleanup; + } + + + // Fast check to avoid expensive computations when not necessary + if (!mbedtls_pk_can_do(&parent.pk, child->sig_pk)) { + log_e("Simple compare failed"); + ret = -1; + goto cleanup; + } + + md_info = mbedtls_md_info_from_type(child->sig_md); + if ( (ret = mbedtls_md( md_info, child->tbs.p, child->tbs.len, hash )) != 0 ) { + log_e("Internal mbedTLS error %X", ret); + goto cleanup; + } + + if ( (ret = mbedtls_pk_verify_ext( child->sig_pk, child->sig_opts, &parent.pk, + child->sig_md, hash, mbedtls_md_get_size( md_info ), + child->sig.p, child->sig.len )) != 0 ) { + + log_e("PK verify failed with error %X", ret); + goto cleanup; + } +cleanup: + mbedtls_x509_crt_free(&parent); + + return ret; +} + + +/* This callback is called for every certificate in the chain. If the chain + * is proper each intermediate certificate is validated through its parent + * in the x509_crt_verify_chain() function. So this callback should + * 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) +{ + mbedtls_x509_crt *child = crt; + + /* It's OK for a trusted cert to have a weak signature hash alg. + as we already trust this certificate */ + uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD); + + if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) { + return 0; + } + + + if (s_crt_bundle.crts == NULL) { + log_e("No certificates in bundle"); + return MBEDTLS_ERR_X509_FATAL_ERROR; + } + + log_d("%d certificates in bundle", s_crt_bundle.num_certs); + + size_t name_len = 0; + const uint8_t *crt_name; + + bool crt_found = false; + 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 */ + 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 (cmp_res == 0) { + crt_found = true; + break; + } else if (cmp_res < 0) { + end = middle - 1; + } else { + start = middle + 1; + } + 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) { + log_i("Certificate validated"); + *flags = 0; + return 0; + } + + log_e("Failed to verify certificate"); + return MBEDTLS_ERR_X509_FATAL_ERROR; +} + + +/* Initialize the bundle into an array so we can do binary search for certs, + 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) +{ + s_crt_bundle.num_certs = (x509_bundle[0] << 8) | x509_bundle[1]; + s_crt_bundle.crts = calloc(s_crt_bundle.num_certs, sizeof(x509_bundle)); + + if (s_crt_bundle.crts == NULL) { + log_e("Unable to allocate memory for bundle"); + return ESP_ERR_NO_MEM; + } + + const uint8_t *cur_crt; + cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET; + + for (int i = 0; i < s_crt_bundle.num_certs; i++) { + s_crt_bundle.crts[i] = cur_crt; + + 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; + } + + 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) { + log_e("Failed to attach bundle"); + return ret; + } + + if (conf) { + /* point to a dummy certificate + * This is only required so that the + * 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_verify(ssl_conf, esp_crt_verify_callback, NULL); + } + + return ret; +} + +void esp_crt_bundle_detach(mbedtls_ssl_config *conf) +{ + free(s_crt_bundle.crts); + s_crt_bundle.crts = NULL; + if (conf) { + mbedtls_ssl_conf_verify(conf, NULL, NULL); + } +} + +void esp_crt_bundle_set(const uint8_t *x509_bundle) +{ + // Free any previously used bundle + free(s_crt_bundle.crts); + esp_crt_bundle_init(x509_bundle); +} diff --git a/libraries/WiFiClientSecure/src/esp_crt_bundle.h b/libraries/WiFiClientSecure/src/esp_crt_bundle.h new file mode 100644 index 00000000000..33fb98b9969 --- /dev/null +++ b/libraries/WiFiClientSecure/src/esp_crt_bundle.h @@ -0,0 +1,68 @@ +// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#ifndef _ESP_CRT_BUNDLE_H_ +#define _ESP_CRT_BUNDLE_H_ + +#include "mbedtls/ssl.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Attach and enable use of a bundle for certificate verification + * + * Attach and enable use of a bundle for certificate verification through a verification callback. + * If no specific bundle has been set through esp_crt_bundle_set() it will default to the + * bundle defined in menuconfig and embedded in the binary. + * + * @param[in] conf The config struct for the SSL connection. + * + * @return + * - ESP_OK if adding certificates was successful. + * - Other if an error occured or an action must be taken by the calling process. + */ +esp_err_t esp_crt_bundle_attach(void *conf); + + +/** + * @brief Disable and dealloc the certification bundle + * + * Removes the certificate verification callback and deallocates used resources + * + * @param[in] conf The config struct for the SSL connection. + */ +void esp_crt_bundle_detach(mbedtls_ssl_config *conf); + + +/** + * @brief Set the default certificate bundle used for verification + * + * Overrides the default certificate bundle. In most use cases the bundle should be + * set through menuconfig. The bundle needs to be sorted by subject name since binary search is + * used to find certificates. + * + * @param[in] x509_bundle A pointer to the certificate bundle. + */ +void esp_crt_bundle_set(const uint8_t *x509_bundle); + + +#ifdef __cplusplus +} +#endif + +#endif //_ESP_CRT_BUNDLE_H_ diff --git a/libraries/WiFiClientSecure/src/ssl_client.cpp b/libraries/WiFiClientSecure/src/ssl_client.cpp index 38783aee2e4..ef198206c6e 100644 --- a/libraries/WiFiClientSecure/src/ssl_client.cpp +++ b/libraries/WiFiClientSecure/src/ssl_client.cpp @@ -17,6 +17,7 @@ #include #include #include "ssl_client.h" +#include "esp_crt_bundle.h" #include "WiFi.h" #ifndef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED @@ -53,14 +54,14 @@ void ssl_init(sslclient_context *ssl_client) } -int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos) +int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, bool useRootCABundle, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos) { char buf[512]; int ret, flags; int enable = 1; log_v("Free internal heap before TLS %u", ESP.getFreeHeap()); - if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) { + if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure && !useRootCABundle) { return -1; } @@ -183,6 +184,13 @@ int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t p mbedtls_x509_crt_free(&ssl_client->ca_cert); return handle_error(ret); } + } else if (useRootCABundle) { + log_v("Attaching root CA cert bundle") + ret = esp_crt_bundle_attach(&ssl_client->ssl_conf); + + if (ret < 0) { + return handle_error(ret); + } } else if (pskIdent != NULL && psKey != NULL) { log_v("Setting up PSK"); // convert PSK from hex to binary diff --git a/libraries/WiFiClientSecure/src/ssl_client.h b/libraries/WiFiClientSecure/src/ssl_client.h index d6be76d18a1..ea89e6bb50a 100644 --- a/libraries/WiFiClientSecure/src/ssl_client.h +++ b/libraries/WiFiClientSecure/src/ssl_client.h @@ -29,7 +29,7 @@ typedef struct sslclient_context { void ssl_init(sslclient_context *ssl_client); -int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos); +int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, bool useRootCABundle, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos); void stop_ssl_socket(sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); int data_to_read(sslclient_context *ssl_client); int send_ssl_data(sslclient_context *ssl_client, const uint8_t *data, size_t len); diff --git a/tools/gen_crt_bundle.py b/tools/gen_crt_bundle.py new file mode 100644 index 00000000000..87e29e61fa4 --- /dev/null +++ b/tools/gen_crt_bundle.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python +# +# ESP32 x509 certificate bundle generation utility +# +# Converts PEM and DER certificates to a custom bundle format which stores just the +# subject name and public key to reduce space +# +# 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... +# +# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import with_statement + +import argparse +import csv +import os +import re +import struct +import sys +from io import open + +try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization +except ImportError: + print('The cryptography package is not installed.' + 'Please refer to the Get Started section of the ESP-IDF Programming Guide for ' + 'setting up the required packages.') + raise + +ca_bundle_bin_file = 'x509_crt_bundle' + +quiet = False + + +def status(msg): + """ Print status message to stderr """ + if not quiet: + critical(msg) + + +def critical(msg): + """ Print critical message to stderr """ + sys.stderr.write('gen_crt_bundle.py: ') + sys.stderr.write(msg) + sys.stderr.write('\n') + + +class CertificateBundle: + def __init__(self): + 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 + for file_path in os.listdir(crts_path): + found |= self.add_from_file(os.path.join(crts_path, file_path)) + + if found is False: + raise InputError('No valid x509 certificates found in %s' % crts_path) + + def add_from_file(self, file_path): + try: + if file_path.endswith('.pem'): + status('Parsing certificates from %s' % file_path) + with open(file_path, 'r', encoding='utf-8') as f: + crt_str = f.read() + self.add_from_pem(crt_str) + return True + + elif file_path.endswith('.der'): + status('Parsing certificates from %s' % file_path) + with open(file_path, 'rb') as f: + crt_str = f.read() + self.add_from_der(crt_str) + return True + + except ValueError: + critical('Invalid certificate in %s' % file_path) + raise InputError('Invalid certificate') + + return False + + def add_from_pem(self, crt_str): + """ A single PEM file may have multiple certificates """ + + crt = '' + count = 0 + start = False + + for strg in crt_str.splitlines(True): + if strg == '-----BEGIN CERTIFICATE-----\n' and start is False: + crt = '' + start = True + elif strg == '-----END CERTIFICATE-----\n' and start is True: + crt += strg + '\n' + start = False + self.certificates.append(x509.load_pem_x509_certificate(crt.encode(), default_backend())) + count += 1 + if start is True: + crt += strg + + if(count == 0): + raise InputError('No certificate found') + + status('Successfully added %d certificates' % count) + + def add_from_der(self, crt_str): + self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend())) + status('Successfully added 1 certificate') + + def create_bundle(self): + # 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)) + + for crt in self.certificates: + """ Read the public key as DER format """ + pub_key = crt.public_key() + pub_key_der = pub_key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo) + + """ Read the subject name as DER format """ + sub_name_der = crt.subject.public_bytes(default_backend()) + + name_len = len(sub_name_der) + key_len = len(pub_key_der) + len_data = struct.pack('>HH', name_len, key_len) + + bundle += len_data + bundle += sub_name_der + bundle += pub_key_der + + return bundle + + def add_with_filter(self, crts_path, filter_path): + + filter_set = set() + with open(filter_path, 'r', encoding='utf-8') as f: + csv_reader = csv.reader(f, delimiter=',') + + # Skip header + next(csv_reader) + for row in csv_reader: + filter_set.add(row[1]) + + status('Parsing certificates from %s' % crts_path) + crt_str = [] + with open(crts_path, 'r', encoding='utf-8') as f: + crt_str = f.read() + + # Split all certs into a list of (name, certificate string) tuples + pem_crts = re.findall(r'(^.+?)\n(=+\n[\s\S]+?END CERTIFICATE-----\n)', crt_str, re.MULTILINE) + + filtered_crts = '' + for name, crt in pem_crts: + if name in filter_set: + filtered_crts += crt + + self.add_from_pem(filtered_crts) + + +class InputError(RuntimeError): + def __init__(self, e): + super(InputError, self).__init__(e) + + +def main(): + global quiet + + parser = argparse.ArgumentParser(description='ESP-IDF x509 certificate bundle utility') + + parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true') + parser.add_argument('--input', '-i', nargs='+', required=True, + 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') + + args = parser.parse_args() + + quiet = args.quiet + + bundle = CertificateBundle() + + for path in args.input: + if os.path.isfile(path): + if os.path.basename(path) == 'cacrt_all.pem' and args.filter: + bundle.add_with_filter(path, args.filter) + else: + bundle.add_from_file(path) + elif os.path.isdir(path): + bundle.add_from_path(path) + else: + raise InputError('Invalid --input=%s, is neither file nor folder' % args.input) + + status('Successfully added %d certificates in total' % len(bundle.certificates)) + + crt_bundle = bundle.create_bundle() + + with open(ca_bundle_bin_file, 'wb') as f: + f.write(crt_bundle) + + +if __name__ == '__main__': + try: + main() + except InputError as e: + print(e) + sys.exit(2) From 653d2ed208b80783ca316cfefb3cb8e5ce393144 Mon Sep 17 00:00:00 2001 From: esp32wrangler <44122875+esp32wrangler@users.noreply.github.com> Date: Fri, 7 Jan 2022 15:13:39 +0100 Subject: [PATCH 2/3] Fix build issues --- CMakeLists.txt | 2 +- libraries/WiFiClientSecure/src/ssl_client.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8f05c22756..ae3f956bee7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,7 @@ set(LIBRARY_SRCS libraries/WebServer/src/Parsing.cpp libraries/WebServer/src/detail/mimetable.cpp libraries/WiFiClientSecure/src/ssl_client.cpp - libraries/WiFiClientSecure/src/esp_crt_bundle.cpp + libraries/WiFiClientSecure/src/esp_crt_bundle.c libraries/WiFiClientSecure/src/WiFiClientSecure.cpp libraries/WiFi/src/WiFiAP.cpp libraries/WiFi/src/WiFiClient.cpp diff --git a/libraries/WiFiClientSecure/src/ssl_client.cpp b/libraries/WiFiClientSecure/src/ssl_client.cpp index ef198206c6e..0d28e85acae 100644 --- a/libraries/WiFiClientSecure/src/ssl_client.cpp +++ b/libraries/WiFiClientSecure/src/ssl_client.cpp @@ -185,7 +185,7 @@ int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t p return handle_error(ret); } } else if (useRootCABundle) { - log_v("Attaching root CA cert bundle") + log_v("Attaching root CA cert bundle"); ret = esp_crt_bundle_attach(&ssl_client->ssl_conf); if (ret < 0) { From 943aab18e909f737588f4039eb71208419b6077d Mon Sep 17 00:00:00 2001 From: esp32wrangler <44122875+esp32wrangler@users.noreply.github.com> Date: Wed, 19 Jan 2022 13:16:12 +0100 Subject: [PATCH 3/3] Clean up old bundle index when NULL bundle is attached --- libraries/WiFiClientSecure/src/WiFiClientSecure.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp b/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp index 7f6057c6d65..4f7bc80263d 100644 --- a/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp +++ b/libraries/WiFiClientSecure/src/WiFiClientSecure.cpp @@ -272,6 +272,7 @@ void WiFiClientSecure::setCACert (const char *rootCA) esp_crt_bundle_set(bundle); _use_ca_bundle = true; } else { + esp_crt_bundle_detach(NULL); _use_ca_bundle = false; } }