diff --git a/27_ota_https_skip_cert_option/.gitignore b/27_ota_https_skip_cert_option/.gitignore new file mode 100644 index 0000000..f759d15 --- /dev/null +++ b/27_ota_https_skip_cert_option/.gitignore @@ -0,0 +1,97 @@ +.config +*.o +*.pyc + +# gtags +GTAGS +GRTAGS +GPATH + +# emacs +.dir-locals.el + +# emacs temp file suffixes +*~ +.#* +\#*# + +# eclipse setting +.settings + +# MacOS directory files +.DS_Store + +# Components Unit Test Apps files +components/**/build +components/**/sdkconfig +components/**/sdkconfig.old + +# Example project files +examples/**/sdkconfig +examples/**/sdkconfig.old +examples/**/build + +# Doc build artifacts +docs/_build/ +docs/doxygen_sqlite3.db + +# Downloaded font files +docs/_static/DejaVuSans.ttf +docs/_static/NotoSansSC-Regular.otf + +# Unit test app files +tools/unit-test-app/sdkconfig +tools/unit-test-app/sdkconfig.old +tools/unit-test-app/build +tools/unit-test-app/builds +tools/unit-test-app/output +tools/unit-test-app/test_configs + +# Unit Test CMake compile log folder +log_ut_cmake + +# test application build files +tools/test_apps/**/build +tools/test_apps/**/sdkconfig +tools/test_apps/**/sdkconfig.old + +# IDF monitor test +tools/test_idf_monitor/outputs + +TEST_LOGS + +# gcov coverage reports +*.gcda +*.gcno +coverage.info +coverage_report/ + +test_multi_heap_host + +# VS Code Settings +.vscode/ + +# VIM files +*.swp +*.swo + +# Clion IDE CMake build & config +.idea/ +cmake-build-*/ + +# Results for the checking of the Python coding style and static analysis +.mypy_cache +flake8_output.txt + +# ESP-IDF default build directory name +build + +# lock files for examples and components +dependencies.lock + +# managed_components for examples +managed_components + +# self config +sdkconfig +sdkconfig.old diff --git a/27_ota_https_skip_cert_option/2m_ota_partitions.csv b/27_ota_https_skip_cert_option/2m_ota_partitions.csv new file mode 100644 index 0000000..117c50c --- /dev/null +++ b/27_ota_https_skip_cert_option/2m_ota_partitions.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, 0xd000, 0x2000 +phy_init, data, phy, 0xf000, 0x1000 +ota_0, 0, ota_0, 0x10000, 0xF0000 +ota_1, 0, ota_1, 0x110000,0xF0000 diff --git a/27_ota_https_skip_cert_option/CMakeLists.txt b/27_ota_https_skip_cert_option/CMakeLists.txt new file mode 100644 index 0000000..bef4509 --- /dev/null +++ b/27_ota_https_skip_cert_option/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hello-world) diff --git a/27_ota_https_skip_cert_option/Makefile b/27_ota_https_skip_cert_option/Makefile new file mode 100644 index 0000000..5108515 --- /dev/null +++ b/27_ota_https_skip_cert_option/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := hello-world + +include $(IDF_PATH)/make/project.mk diff --git a/27_ota_https_skip_cert_option/README.md b/27_ota_https_skip_cert_option/README.md new file mode 100644 index 0000000..8152b60 --- /dev/null +++ b/27_ota_https_skip_cert_option/README.md @@ -0,0 +1,63 @@ +# 【OTA HTTP(S)】无线远程升级支持跳过证书升级 + +本工程由半颗心脏编程并开源,使用的主要协议栈有 mbedtls 和 esp_ota 。 + +源码地址:https://github.com/xuhongv/StudyInEsp32/tree/master/27_ota_https_skip_cert_option + +# 硬件要求 + +安信可在售 ESP32/S3/C3 模组 + +# 软件版本 + +esp-idf 版本: + +``` +commit 8ffddf53bc9cb0c36d1949476e244b202f3b42d2 (origin/release/v4.3) +``` +# 如何使用此Demo +- 先配置路由器信息。 +- 修改ota文件的URL,并且修改是否需要跳过证书。 +# API 说明 + +见注释: + +``` + typedef enum + { + OTA_CERT_SSL_VERIFY_NONE = 0, // 不校验证书,直接通过 + OTA_CERT_SSL_VERIFY_OPTIONAL, // 校验证书并给出结果,由用户决定是否继续请求 + OTA_CERT_SSL_VERIFY_REQUIRED, // 校验证书并给出结果,必须证书通过才继续请求 + } ota_ssl_cert_verify_set_t; + + /** + * @brief otas_http_client_config configuration + */ + typedef struct + { + const char *url; + ota_ssl_cert_verify_set_t cert_set; + bool skip_ssl_cert_set; //是否跳过证书认证,仅当 ota_ssl_cert_verify_set_t 为 OTA_CERT_SSL_VERIFY_OPTIONAL有效 + int url_length; + } otas_http_client_config; + + esp_err_t start_https_ota(const otas_http_client_config *config); +``` + +# 常见问题 FAQ + +### 1. 如何替换证书? + +请把域名证书替换 https_ota\cert\server_root_cert.pem 里面内容即可。 + +### 2. 如何支持HTTPS连接,但不做证书校验? + +参数开始时候,请把 cert_set 设置为 OTA_CERT_SSL_VERIFY_OPTIONAL ,把 skip_ssl_cert_set设置为 false 。 +``` + .cert_set = OTA_CERT_SSL_VERIFY_OPTIONAL, + .skip_ssl_cert_set = false, +``` + + + + diff --git a/27_ota_https_skip_cert_option/components/https_ota/CMakeLists.txt b/27_ota_https_skip_cert_option/components/https_ota/CMakeLists.txt new file mode 100644 index 0000000..462f24c --- /dev/null +++ b/27_ota_https_skip_cert_option/components/https_ota/CMakeLists.txt @@ -0,0 +1,13 @@ +# set(require_components ${IDF_TARGET} mqtt mdns esp_http_client json freertos spiffs lwip +# bootloader_support app_update openssl wpa_supplicant spi_flash esp_http_server bt esp32c3 esp32) + +set(require_components ${IDF_TARGET} app_update freertos lwip mbedtls lwip nghttp) + +file(GLOB_RECURSE src src/*.c) + +idf_component_register ( + SRCS ${src} + INCLUDE_DIRS "include" + REQUIRES ${require_components} + EMBED_TXTFILES cert/server_root_cert.pem +) \ No newline at end of file diff --git a/27_ota_https_skip_cert_option/components/https_ota/cert/server_root_cert.pem b/27_ota_https_skip_cert_option/components/https_ota/cert/server_root_cert.pem new file mode 100644 index 0000000..0002462 --- /dev/null +++ b/27_ota_https_skip_cert_option/components/https_ota/cert/server_root_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/27_ota_https_skip_cert_option/components/https_ota/component.mk b/27_ota_https_skip_cert_option/components/https_ota/component.mk new file mode 100644 index 0000000..28d1b21 --- /dev/null +++ b/27_ota_https_skip_cert_option/components/https_ota/component.mk @@ -0,0 +1,6 @@ + +COMPONENT_SRCDIRS := src + +COMPONENT_ADD_INCLUDEDIRS := include + +COMPONENT_EMBED_TXTFILES := cert/server_root_cert.pem diff --git a/27_ota_https_skip_cert_option/components/https_ota/include/https_ota.h b/27_ota_https_skip_cert_option/components/https_ota/include/https_ota.h new file mode 100644 index 0000000..a1a7c22 --- /dev/null +++ b/27_ota_https_skip_cert_option/components/https_ota/include/https_ota.h @@ -0,0 +1,53 @@ +/* + * @Author: https://github.com/xuhongv + * @Date: 2022-03-18 07:41:55 + * @LastEditTime: 2022-03-19 14:06:52 + * @LastEditors: Please set LastEditors + * @Description: ota https 头文件 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef enum + { + OTA_CERT_SSL_VERIFY_NONE = 0, // 不校验证书,直接通过 + OTA_CERT_SSL_VERIFY_OPTIONAL, // 校验证书并给出结果,由用户决定是否继续请求 + OTA_CERT_SSL_VERIFY_REQUIRED, // 校验证书并给出结果,必须证书通过才继续请求 + } ota_ssl_cert_verify_set_t; + + /** + * @brief otas_http_client_config configuration + */ + typedef struct + { + const char *url; + ota_ssl_cert_verify_set_t cert_set; + bool skip_ssl_cert_set; //是否跳过证书认证,仅当 ota_ssl_cert_verify_set_t 为 OTA_CERT_SSL_VERIFY_OPTIONAL有效 + + int url_length; + + } otas_http_client_config; + + typedef struct + { + + char path[100]; + + char version[20]; + int port; + char token[80]; + char host[20]; + + } ota_info; + ota_info ota_info_item; + + esp_err_t start_https_ota(const otas_http_client_config *config); + +#ifdef __cplusplus +} +#endif diff --git a/27_ota_https_skip_cert_option/components/https_ota/src/https_ota.c b/27_ota_https_skip_cert_option/components/https_ota/src/https_ota.c new file mode 100644 index 0000000..17ba949 --- /dev/null +++ b/27_ota_https_skip_cert_option/components/https_ota/src/https_ota.c @@ -0,0 +1,562 @@ +// Copyright 2017-2018 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 +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "mbedtls/platform.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/esp_debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" +#include "mbedtls/certs.h" +#include "esp_crt_bundle.h" +#include "esp_ota_ops.h" +#include "https_ota.h" + +static const char *TAG = "ota:"; + +static const char *REQUEST_FORMAT = "GET %s HTTP/1.0\r\n" + "Host: %s \r\n" + "User-Agent: csdn xuhongv\r\n" + "Content-Type: application/json\r\n" + "\r\n"; + +extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start"); +extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_pem_end"); + +#define BUFFSIZE 1024 * 8 // 8KB + +typedef enum esp_ota_firm_state +{ + ESP_OTA_INIT = 0, + ESP_OTA_PREPARE, + ESP_OTA_START, + ESP_OTA_RECVED, + ESP_OTA_FINISH, +} esp_ota_firm_state_t; + +typedef struct esp_ota_firm +{ + uint8_t ota_num; + uint8_t update_ota_num; + esp_ota_firm_state_t state; + size_t content_len; + size_t read_bytes; + size_t write_bytes; + size_t ota_size; + size_t ota_offset; + const char *buf; + size_t bytes; +} esp_ota_firm_t; + +/*an ota data write buffer ready to write to the flash*/ +static char ota_write_data[BUFFSIZE + 1] = {0}; +/* an image total length*/ +static int binary_file_length = 0; + +/*read buffer by byte still delim ,return read bytes counts*/ +static int read_until(const char *buffer, char delim, int len) +{ + // /*TODO: delim check,buffer check,further: do an buffer length limited*/ + int i = 0; + while (buffer[i] != delim && i < len) + { + ++i; + } + return i + 1; +} + +static void __attribute__((noreturn)) task_fatal_error() +{ + ESP_LOGE(TAG, "Exiting task due to fatal error..."); + + (void)vTaskDelete(NULL); + + while (1) + { + ; + } +} + +bool _esp_ota_firm_parse_http(esp_ota_firm_t *ota_firm, const char *text, size_t total_len, size_t *parse_len) +{ + + /* i means current position */ + int i = 0, i_read_len = 0; + char *ptr = NULL, *ptr2 = NULL; + char length_str[32]; + + while (text[i] != 0 && i < total_len) + { + if (ota_firm->content_len == 0 && (ptr = (char *)strstr(text, "Content-Length")) != NULL) + { + ptr += 16; + ptr2 = (char *)strstr(ptr, "\r\n"); + memset(length_str, 0, sizeof(length_str)); + memcpy(length_str, ptr, ptr2 - ptr); + ota_firm->content_len = atoi(length_str); + ota_firm->ota_size = ota_firm->content_len; + ota_firm->ota_offset = 0; + ESP_LOGI(TAG, "parse Content-Length:%d, ota_size %d", ota_firm->content_len, ota_firm->ota_size); + } + + i_read_len = read_until(&text[i], '\n', total_len - i); + + if (i_read_len > total_len - i) + { + ESP_LOGE(TAG, "recv malformed http header"); + task_fatal_error(); + } + + // if resolve \r\n line, http header is finished + if (i_read_len == 2) + { + if (ota_firm->content_len == 0) + { + ESP_LOGE(TAG, "did not parse Content-Length item"); + task_fatal_error(); + } + + *parse_len = i + 2; + + return true; + } + + i += i_read_len; + } + + return false; +} + +static size_t esp_ota_firm_do_parse_msg(esp_ota_firm_t *ota_firm, const char *in_buf, size_t in_len) +{ + size_t tmp; + size_t parsed_bytes = in_len; + + switch (ota_firm->state) + { + case ESP_OTA_INIT: + if (_esp_ota_firm_parse_http(ota_firm, in_buf, in_len, &tmp)) + { + ota_firm->state = ESP_OTA_PREPARE; + ESP_LOGD(TAG, "Http parse %d bytes", tmp); + parsed_bytes = tmp; + } + break; + case ESP_OTA_PREPARE: + ota_firm->read_bytes += in_len; + + if (ota_firm->read_bytes >= ota_firm->ota_offset) + { + ota_firm->buf = &in_buf[in_len - (ota_firm->read_bytes - ota_firm->ota_offset)]; + ota_firm->bytes = ota_firm->read_bytes - ota_firm->ota_offset; + ota_firm->write_bytes += ota_firm->read_bytes - ota_firm->ota_offset; + ota_firm->state = ESP_OTA_START; + ESP_LOGD(TAG, "Receive %d bytes and start to update", ota_firm->read_bytes); + ESP_LOGD(TAG, "Write %d total %d", ota_firm->bytes, ota_firm->write_bytes); + } + + break; + case ESP_OTA_START: + if (ota_firm->write_bytes + in_len > ota_firm->ota_size) + { + ota_firm->bytes = ota_firm->ota_size - ota_firm->write_bytes; + ota_firm->state = ESP_OTA_RECVED; + } + else + ota_firm->bytes = in_len; + + ota_firm->buf = in_buf; + + ota_firm->write_bytes += ota_firm->bytes; + + ESP_LOGD(TAG, "Write %d total %d", ota_firm->bytes, ota_firm->write_bytes); + + break; + case ESP_OTA_RECVED: + parsed_bytes = 0; + ota_firm->state = ESP_OTA_FINISH; + break; + default: + parsed_bytes = 0; + ESP_LOGD(TAG, "State is %d", ota_firm->state); + break; + } + + return parsed_bytes; +} + +static void esp_ota_firm_parse_msg(esp_ota_firm_t *ota_firm, const char *in_buf, size_t in_len) +{ + size_t parse_bytes = 0; + ESP_LOGD(TAG, "Input %d bytes", in_len); + do + { + size_t bytes = esp_ota_firm_do_parse_msg(ota_firm, in_buf + parse_bytes, in_len - parse_bytes); + ESP_LOGD(TAG, "Parse %d bytes", bytes); + if (bytes) + parse_bytes += bytes; + } while (parse_bytes != in_len); +} + +static inline int esp_ota_firm_is_finished(esp_ota_firm_t *ota_firm) +{ + return (ota_firm->state == ESP_OTA_FINISH || ota_firm->state == ESP_OTA_RECVED); +} + +static inline int esp_ota_firm_can_write(esp_ota_firm_t *ota_firm) +{ + return (ota_firm->state == ESP_OTA_START || ota_firm->state == ESP_OTA_RECVED); +} + +static inline const char *esp_ota_firm_get_write_buf(esp_ota_firm_t *ota_firm) +{ + return ota_firm->buf; +} + +static inline size_t esp_ota_firm_get_write_bytes(esp_ota_firm_t *ota_firm) +{ + return ota_firm->bytes; +} + +static void esp_ota_firm_init(esp_ota_firm_t *ota_firm, const esp_partition_t *update_partition) +{ + memset(ota_firm, 0, sizeof(esp_ota_firm_t)); + ota_firm->state = ESP_OTA_INIT; + ota_firm->update_ota_num = update_partition->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_0; + ESP_LOGI(TAG, "Totoal OTA number %d update to %d part", ota_firm->ota_num, ota_firm->update_ota_num); +} + +static int get_port(const char *url, struct http_parser_url *u) +{ + if (u->field_data[UF_PORT].len) + { + return strtol(&url[u->field_data[UF_PORT].off], NULL, 10); + } + else + { + if (strncasecmp(&url[u->field_data[UF_SCHEMA].off], "http", u->field_data[UF_SCHEMA].len) == 0) + { + return 80; + } + else if (strncasecmp(&url[u->field_data[UF_SCHEMA].off], "https", u->field_data[UF_SCHEMA].len) == 0) + { + return 443; + } + } + return 0; +} + +esp_err_t start_https_ota(const otas_http_client_config *config) +{ + + char buf[1024], s_port[6]; + int len = 0, flags = 0, ret = 0; + esp_err_t err = ESP_FAIL; + + if (config) + { + ESP_LOGI(TAG, "get length: %d", (config->url_length)); + ESP_LOGI(TAG, "get url: %s", (config->url)); + } + else + ESP_LOGI(TAG, "get url is NULL"); + + struct http_parser_url u; + http_parser_url_init(&u); + http_parser_parse_url(config->url, config->url_length, 0, &u); + char *use_host = strndup(&(config->url[u.field_data[UF_HOST].off]), u.field_data[UF_HOST].len); + sprintf(s_port, "%d", get_port(config->url, &u)); + + ESP_LOGI(TAG, "host:%s", use_host); + ESP_LOGI(TAG, "path: %s", &(config->url[u.field_data[UF_PATH].off])); + ESP_LOGI(TAG, "port: %s", s_port); + + /********************************************/ + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_x509_crt cacert; + mbedtls_ssl_config conf; + mbedtls_net_context server_fd; + + char *REQUEST = (char *)malloc(512); + sprintf(REQUEST, REQUEST_FORMAT, &(config->url[u.field_data[UF_PATH].off]), use_host); + + ESP_LOGI(TAG, "http header: \n%s\n", REQUEST); + + /******************** OTA start set ************************/ + /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */ + /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */ + + esp_ota_handle_t update_handle = 0; + const esp_partition_t *update_partition = NULL; + + ESP_LOGI(TAG, "Starting OTA example"); + + const esp_partition_t *configured = esp_ota_get_boot_partition(); + const esp_partition_t *running = esp_ota_get_running_partition(); + + if (configured != running) + { + ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", configured->address, running->address); + ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); + } + ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)", running->type, running->subtype, running->address); + ESP_LOGI(TAG, "initting ... this may take a while.)"); + + /****************** OTA start end **************************/ + + mbedtls_ssl_init(&ssl); + mbedtls_x509_crt_init(&cacert); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_ssl_config_init(&conf); + mbedtls_entropy_init(&entropy); + + if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + NULL, 0)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret); + abort(); + } + + ESP_LOGI(TAG, "Loading the CA root certificate..."); + + ret = mbedtls_x509_crt_parse(&cacert, server_root_cert_pem_start, server_root_cert_pem_end - server_root_cert_pem_start); + + if (ret < 0) + { + ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); + abort(); + } + + ESP_LOGI(TAG, "Setting hostname for TLS session..."); + + /* Hostname set here should match CN in server certificate */ + if ((ret = mbedtls_ssl_set_hostname(&ssl, use_host)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret); + abort(); + } + + ESP_LOGI(TAG, "Setting up the SSL/TLS structure..."); + + if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret); + goto exit; + } + + /* MBEDTLS_SSL_VERIFY_OPTIONAL is bad for security, in this example it will print + a warning if CA verification fails but it will continue to connect. + You should consider using MBEDTLS_SSL_VERIFY_REQUIRED in your own code. + */ + mbedtls_ssl_conf_authmode(&conf, config->cert_set); + if (config->cert_set != OTA_CERT_SSL_VERIFY_NONE) + { + mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); + } + + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret); + goto exit; + } + mbedtls_net_init(&server_fd); + + ESP_LOGI(TAG, "Connecting to %s:%s...", use_host, s_port); + + if ((ret = mbedtls_net_connect(&server_fd, use_host, s_port, MBEDTLS_NET_PROTO_TCP)) != 0) + { + ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Connected."); + + mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + ESP_LOGI(TAG, "Performing the SSL/TLS handshake..."); + + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) + { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) + { + ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret); + goto exit; + } + } + + if (config->cert_set != OTA_CERT_SSL_VERIFY_NONE) + { + ESP_LOGI(TAG, "Verifying peer X.509 certificate..."); + //获取校验证书结果 + if (mbedtls_ssl_get_verify_result(&ssl) != 0) + { + /* In real life, we probably want to close connection if ret != 0 */ + ESP_LOGW(TAG, "Failed to verify peer certificate!"); + bzero(buf, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + ESP_LOGW(TAG, "verification flags: %d , info: %s", flags, buf); + if ((config->cert_set == OTA_CERT_SSL_VERIFY_OPTIONAL && config->skip_ssl_cert_set) || config->cert_set == OTA_CERT_SSL_VERIFY_REQUIRED) + { + err = ESP_FAIL; + goto exit; + } + } + else + { + ESP_LOGI(TAG, "Certificate verified success."); + } + } + else + { + ESP_LOGI(TAG, "Verifying peer X.509 not certificate..."); + } + + ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(&ssl)); + + ESP_LOGI(TAG, "Writing HTTP request..."); + + size_t written_bytes = 0; + + do + { + ret = mbedtls_ssl_write(&ssl, (const unsigned char *)REQUEST + written_bytes, strlen(REQUEST) - written_bytes); + if (ret >= 0) + { + ESP_LOGI(TAG, "%d bytes written", ret); + written_bytes += ret; + } + else if (ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != MBEDTLS_ERR_SSL_WANT_READ) + { + ESP_LOGE(TAG, "mbedtls_ssl_write returned -0x%x", -ret); + goto exit; + } + } while (written_bytes < strlen(REQUEST)); + + update_partition = esp_ota_get_next_update_partition(NULL); + + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", update_partition->subtype, update_partition->address); + assert(update_partition != NULL); + + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_begin failed, error=%d", err); + task_fatal_error(); + } + ESP_LOGI(TAG, "esp_ota_begin succeeded"); + + esp_ota_firm_t ota_firm; + esp_ota_firm_init(&ota_firm, update_partition); + + ESP_LOGI(TAG, "Reading HTTP response..."); + + do + { + memset(ota_write_data, 0, BUFFSIZE); + + len = mbedtls_ssl_read(&ssl, (unsigned char *)buf, BUFFSIZE); + + if (len == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) + continue; + + if (len == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) + { + len = 0; + break; + } + else if (len > 0) + { + + esp_ota_firm_parse_msg(&ota_firm, buf, len); + + if (!esp_ota_firm_can_write(&ota_firm)) + continue; + + memcpy(ota_write_data, esp_ota_firm_get_write_buf(&ota_firm), esp_ota_firm_get_write_bytes(&ota_firm)); + len = esp_ota_firm_get_write_bytes(&ota_firm); + + err = esp_ota_write(update_handle, (const void *)ota_write_data, len); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err); + task_fatal_error(); + } + binary_file_length += len; + ESP_LOGI(TAG, "Have written image length %d", binary_file_length); + } + else if (len < 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_read returned -0x%x", -ret); + break; + } + else if (len == 0) + { + ESP_LOGI(TAG, "connection closed"); + break; + } + + if (esp_ota_firm_is_finished(&ota_firm)) + break; + + } while (1); + + mbedtls_ssl_close_notify(&ssl); + + ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length); + + if (esp_ota_end(update_handle) != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_end failed!"); + task_fatal_error(); + } + err = esp_ota_set_boot_partition(update_partition); + + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%x", err); + task_fatal_error(); + goto exit; + } + +exit: + + mbedtls_ssl_session_reset(&ssl); + mbedtls_net_free(&server_fd); + + free(REQUEST); + free(use_host); + + return err; +} diff --git a/27_ota_https_skip_cert_option/main/CMakeLists.txt b/27_ota_https_skip_cert_option/main/CMakeLists.txt new file mode 100644 index 0000000..8a3ab69 --- /dev/null +++ b/27_ota_https_skip_cert_option/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS "") diff --git a/27_ota_https_skip_cert_option/main/component.mk b/27_ota_https_skip_cert_option/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/27_ota_https_skip_cert_option/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/27_ota_https_skip_cert_option/main/main.c b/27_ota_https_skip_cert_option/main/main.c new file mode 100644 index 0000000..35f7c02 --- /dev/null +++ b/27_ota_https_skip_cert_option/main/main.c @@ -0,0 +1,115 @@ +/* + * @Author: your name + * @Date: 2022-03-18 07:41:55 + * @LastEditTime: 2022-03-19 14:20:38 + * @LastEditors: Please set LastEditors + * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + * @FilePath: \esp-idf\examples\get-started\sample_project\main\main.c + */ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_event_loop.h" +#include "esp_err.h" + +#include "mbedtls/aes.h" +#include "mbedtls/sha256.h" +#include "mbedtls/compat-1.3.h" +#include "esp_wifi.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "driver/uart.h" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/apps/sntp.h" +#include "lwip/apps/sntp_opts.h" +#include "mqtt_client.h" +#include "esp_tls.h" +#include "esp_ota_ops.h" +#include "nvs.h" +#include "cJSON.h" +#include "nvs_flash.h" +#include "esp_http_client.h" +#include "tcpip_adapter.h" +#include "https_ota.h" + +#include "protocol_examples_common.h" + +static const char *TAG = "MAIN_FILE:"; + +static void TaskOTAHttp(void *p) +{ + char URL[] = {"https://testaithinker.oss-cn-beijing.aliyuncs.com/esp32_ota.bin"}; + + const otas_http_client_config config = { + .cert_set = OTA_CERT_SSL_VERIFY_OPTIONAL, + .skip_ssl_cert_set = false, + .url_length = strlen(URL), + .url = URL, + }; + + ESP_LOGI(TAG, "Free memory: %d bytes", esp_get_free_heap_size()); + + if (start_https_ota(&config) == ESP_OK) + { + static int request_count; + ESP_LOGI(TAG, "Completed %d requests and restart ", ++request_count); + for (int countdown = 3; countdown >= 0; countdown--) + { + ESP_LOGI(TAG, "%d...", countdown); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + esp_restart(); + } + else + { + ESP_LOGE(TAG, "OTA Fail"); + ESP_LOGI(TAG, "Free memory: %d bytes", esp_get_free_heap_size()); + } + + vTaskDelete(NULL); +} + +void app_main(void) +{ + // Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "Free memory: %d bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "date:%s,time:%s\n", __DATE__, __TIME__); + + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + ESP_ERROR_CHECK(example_connect()); + + ESP_LOGI(TAG, "Free memory: %d bytes", esp_get_free_heap_size()); + + ESP_LOGI(TAG, "Connected to AP, begin http..."); + + xTaskCreate(&TaskOTAHttp, "TaskOTAHttp", 1024 * 10, NULL, 5, NULL); +} diff --git a/27_ota_https_skip_cert_option/sdkconfig.defaults b/27_ota_https_skip_cert_option/sdkconfig.defaults new file mode 100644 index 0000000..0d5b444 --- /dev/null +++ b/27_ota_https_skip_cert_option/sdkconfig.defaults @@ -0,0 +1,28 @@ + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="2m_ota_partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="2m_ota_partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Example Connection Configuration +# +CONFIG_EXAMPLE_CONNECT_WIFI=y +CONFIG_EXAMPLE_WIFI_SSID="AIOT" +CONFIG_EXAMPLE_WIFI_PASSWORD="1234567890" +# CONFIG_EXAMPLE_CONNECT_ETHERNET is not set +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK=y +# CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL is not set +# CONFIG_EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL is not set +# CONFIG_EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL is not set +# end of Example Connection Configuration + +