فهرست منبع

Merge branch 'feature/esp_https_ota_improvements' into 'release/v3.3'

esp_https_ota: component refactoring, bugfixes and feature additions (backport v3.3)

See merge request espressif/esp-idf!6047
Jiang Jiang Jian 6 سال پیش
والد
کامیت
4e27cbb89f

+ 17 - 9
components/esp-tls/esp_tls.c

@@ -242,18 +242,26 @@ static int create_ssl_handle(esp_tls_t *tls, const char *hostname, size_t hostle
         goto exit;        
         goto exit;        
     }
     }
     
     
-    /* Hostname set here should match CN in server certificate */    
-    char *use_host = strndup(hostname, hostlen);
-    if (!use_host) {
-        goto exit;
-    }
+    if (!cfg->skip_common_name) {
+        char *use_host = NULL;
+        if (cfg->common_name != NULL) {
+            use_host = strndup(cfg->common_name, strlen(cfg->common_name));
+        } else {
+            use_host = strndup(hostname, hostlen);
+        }
 
 
-    if ((ret = mbedtls_ssl_set_hostname(&tls->ssl, use_host)) != 0) {
-        ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
+        if (use_host == NULL) {
+            goto exit;
+        }
+
+        /* Hostname set here should match CN in server certificate */
+        if ((ret = mbedtls_ssl_set_hostname(&tls->ssl, use_host)) != 0) {
+            ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
+            free(use_host);
+            goto exit;
+        }
         free(use_host);
         free(use_host);
-        goto exit;
     }
     }
-    free(use_host);
 
 
     if ((ret = mbedtls_ssl_config_defaults(&tls->conf,
     if ((ret = mbedtls_ssl_config_defaults(&tls->conf,
                     MBEDTLS_SSL_IS_CLIENT,
                     MBEDTLS_SSL_IS_CLIENT,

+ 11 - 3
components/esp-tls/esp_tls.h

@@ -57,17 +57,20 @@ typedef struct esp_tls_cfg {
                                                  - where the first '2' is the length of the protocol and
                                                  - where the first '2' is the length of the protocol and
                                                  - the subsequent 'h2' is the protocol name */
                                                  - the subsequent 'h2' is the protocol name */
  
  
-    const unsigned char *cacert_pem_buf;    /*!< Certificate Authority's certificate in a buffer */
+    const unsigned char *cacert_pem_buf;    /*!< Certificate Authority's certificate in a buffer.
+                                                 This buffer should be NULL terminated */
  
  
     unsigned int cacert_pem_bytes;          /*!< Size of Certificate Authority certificate
     unsigned int cacert_pem_bytes;          /*!< Size of Certificate Authority certificate
                                                  pointed to by cacert_pem_buf */
                                                  pointed to by cacert_pem_buf */
 
 
-    const unsigned char *clientcert_pem_buf;/*!< Client certificate in a buffer */
+    const unsigned char *clientcert_pem_buf;/*!< Client certificate in a buffer
+                                                 This buffer should be NULL terminated */
  
  
     unsigned int clientcert_pem_bytes;      /*!< Size of client certificate pointed to by
     unsigned int clientcert_pem_bytes;      /*!< Size of client certificate pointed to by
                                                  clientcert_pem_buf */
                                                  clientcert_pem_buf */
 
 
-    const unsigned char *clientkey_pem_buf; /*!< Client key in a buffer */
+    const unsigned char *clientkey_pem_buf; /*!< Client key in a buffer
+                                                 This buffer should be NULL terminated */
 
 
     unsigned int clientkey_pem_bytes;       /*!< Size of client key pointed to by
     unsigned int clientkey_pem_bytes;       /*!< Size of client key pointed to by
                                                  clientkey_pem_buf */
                                                  clientkey_pem_buf */
@@ -85,6 +88,11 @@ typedef struct esp_tls_cfg {
 
 
     bool use_global_ca_store;               /*!< Use a global ca_store for all the connections in which
     bool use_global_ca_store;               /*!< Use a global ca_store for all the connections in which
                                                  this bool is set. */
                                                  this bool is set. */
+
+    const char *common_name;                /*!< If non-NULL, server certificate CN must match this name.
+                                                 If NULL, server certificate CN must match hostname. */
+
+    bool skip_common_name;                  /*!< Skip any validation of server certificate CN field */
 } esp_tls_cfg_t;
 } esp_tls_cfg_t;
 
 
 /**
 /**

+ 10 - 0
components/esp32/esp_err_to_name.c

@@ -19,6 +19,9 @@
 #if __has_include("esp_http_server.h")
 #if __has_include("esp_http_server.h")
 #include "esp_http_server.h"
 #include "esp_http_server.h"
 #endif
 #endif
+#if __has_include("esp_https_ota.h")
+#include "esp_https_ota.h"
+#endif
 #if __has_include("esp_image_format.h")
 #if __has_include("esp_image_format.h")
 #include "esp_image_format.h"
 #include "esp_image_format.h"
 #endif
 #endif
@@ -531,6 +534,13 @@ static const esp_err_msg_t esp_err_msg_table[] = {
 #   endif
 #   endif
 #   ifdef      ESP_ERR_HTTPD_TASK
 #   ifdef      ESP_ERR_HTTPD_TASK
     ERR_TBL_IT(ESP_ERR_HTTPD_TASK),                         /* 32776 0x8008 Failed to launch server task/thread */
     ERR_TBL_IT(ESP_ERR_HTTPD_TASK),                         /* 32776 0x8008 Failed to launch server task/thread */
+#   endif
+    // components/esp_https_ota/include/esp_https_ota.h
+#   ifdef      ESP_ERR_HTTPS_OTA_BASE
+    ERR_TBL_IT(ESP_ERR_HTTPS_OTA_BASE),                     /* 36864 0x9000 */
+#   endif
+#   ifdef      ESP_ERR_HTTPS_OTA_IN_PROGRESS
+    ERR_TBL_IT(ESP_ERR_HTTPS_OTA_IN_PROGRESS),              /* 36865 0x9001 */
 #   endif
 #   endif
     // components/spi_flash/include/esp_spi_flash.h
     // components/spi_flash/include/esp_spi_flash.h
 #   ifdef      ESP_ERR_FLASH_BASE
 #   ifdef      ESP_ERR_FLASH_BASE

+ 7 - 0
components/esp_http_client/Kconfig

@@ -7,4 +7,11 @@ menu "ESP HTTP client"
         help
         help
             This option will enable https protocol by linking mbedtls library and initializing SSL transport
             This option will enable https protocol by linking mbedtls library and initializing SSL transport
 
 
+    config ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
+        bool "Enable HTTP Basic Authentication"
+        default n
+        help
+            This option will enable HTTP Basic Authentication. It is disabled by default as Basic
+            auth uses unencrypted encoding, so it introduces a vulnerability when not using TLS
+
 endmenu
 endmenu

+ 52 - 50
components/esp_http_client/esp_http_client.c

@@ -152,20 +152,6 @@ static const char *HTTP_METHOD_MAPPING[] = {
     "OPTIONS"
     "OPTIONS"
 };
 };
 
 
-/**
- * Enum for the HTTP status codes.
- */
-enum HttpStatus_Code
-{
-    /* 3xx - Redirection */
-    HttpStatus_MovedPermanently  = 301,
-    HttpStatus_Found             = 302,
-
-    /* 4xx - Client Error */
-    HttpStatus_Unauthorized      = 401
-};
-
-
 static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client, int write_len);
 static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client, int write_len);
 static esp_err_t esp_http_client_connect(esp_http_client_handle_t client);
 static esp_err_t esp_http_client_connect(esp_http_client_handle_t client);
 static esp_err_t esp_http_client_send_post_data(esp_http_client_handle_t client);
 static esp_err_t esp_http_client_send_post_data(esp_http_client_handle_t client);
@@ -551,6 +537,10 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
     if (config->client_key_pem) {
     if (config->client_key_pem) {
         esp_transport_ssl_set_client_key_data(ssl, config->client_key_pem, strlen(config->client_key_pem));
         esp_transport_ssl_set_client_key_data(ssl, config->client_key_pem, strlen(config->client_key_pem));
     }
     }
+
+    if (config->skip_cert_common_name_check) {
+        esp_transport_ssl_skip_common_name_check(ssl);
+    }
 #endif
 #endif
 
 
     if (_set_config(client, config) != ESP_OK) {
     if (_set_config(client, config) != ESP_OK) {
@@ -648,13 +638,12 @@ esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client)
     if (client->location == NULL) {
     if (client->location == NULL) {
         return ESP_ERR_INVALID_ARG;
         return ESP_ERR_INVALID_ARG;
     }
     }
+    ESP_LOGD(TAG, "Redirect to %s", client->location);
     return esp_http_client_set_url(client, client->location);
     return esp_http_client_set_url(client, client->location);
 }
 }
 
 
 static esp_err_t esp_http_check_response(esp_http_client_handle_t client)
 static esp_err_t esp_http_check_response(esp_http_client_handle_t client)
 {
 {
-    char *auth_header = NULL;
-
     if (client->redirect_counter >= client->max_redirection_count || client->disable_auto_redirect) {
     if (client->redirect_counter >= client->max_redirection_count || client->disable_auto_redirect) {
         ESP_LOGE(TAG, "Error, reach max_redirection_count count=%d", client->redirect_counter);
         ESP_LOGE(TAG, "Error, reach max_redirection_count count=%d", client->redirect_counter);
         return ESP_ERR_HTTP_MAX_REDIRECT;
         return ESP_ERR_HTTP_MAX_REDIRECT;
@@ -662,44 +651,12 @@ static esp_err_t esp_http_check_response(esp_http_client_handle_t client)
     switch (client->response->status_code) {
     switch (client->response->status_code) {
         case HttpStatus_MovedPermanently:
         case HttpStatus_MovedPermanently:
         case HttpStatus_Found:
         case HttpStatus_Found:
-            ESP_LOGI(TAG, "Redirect to %s", client->location);
-            esp_http_client_set_url(client, client->location);
+            esp_http_client_set_redirection(client);
             client->redirect_counter ++;
             client->redirect_counter ++;
             client->process_again = 1;
             client->process_again = 1;
             break;
             break;
         case HttpStatus_Unauthorized:
         case HttpStatus_Unauthorized:
-            auth_header = client->auth_header;
-            if (auth_header) {
-                http_utils_trim_whitespace(&auth_header);
-                ESP_LOGD(TAG, "UNAUTHORIZED: %s", auth_header);
-                client->redirect_counter ++;
-                if (http_utils_str_starts_with(auth_header, "Digest") == 0) {
-                    ESP_LOGD(TAG, "type = Digest");
-                    client->connection_info.auth_type = HTTP_AUTH_TYPE_DIGEST;
-                } else if (http_utils_str_starts_with(auth_header, "Basic") == 0) {
-                    ESP_LOGD(TAG, "type = Basic");
-                    client->connection_info.auth_type = HTTP_AUTH_TYPE_BASIC;
-                } else {
-                    client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
-                    ESP_LOGE(TAG, "This authentication method is not supported: %s", auth_header);
-                    break;
-                }
-
-                _clear_auth_data(client);
-
-                client->auth_data->method = strdup(HTTP_METHOD_MAPPING[client->connection_info.method]);
-
-                client->auth_data->nc = 1;
-                client->auth_data->realm = http_utils_get_string_between(auth_header, "realm=\"", "\"");
-                client->auth_data->algorithm = http_utils_get_string_between(auth_header, "algorithm=", ",");
-                client->auth_data->qop = http_utils_get_string_between(auth_header, "qop=\"", "\"");
-                client->auth_data->nonce = http_utils_get_string_between(auth_header, "nonce=\"", "\"");
-                client->auth_data->opaque = http_utils_get_string_between(auth_header, "opaque=\"", "\"");
-                client->process_again = 1;
-            } else {
-                client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
-                ESP_LOGW(TAG, "This request requires authentication, but does not provide header information for that");
-            }
+            esp_http_client_add_auth(client);
     }
     }
     return ESP_OK;
     return ESP_OK;
 }
 }
@@ -1271,3 +1228,48 @@ esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_h
         return HTTP_TRANSPORT_UNKNOWN;
         return HTTP_TRANSPORT_UNKNOWN;
     }
     }
 }
 }
+
+void esp_http_client_add_auth(esp_http_client_handle_t client)
+{
+    if (client == NULL) {
+        return;
+    }
+    if (client->state != HTTP_STATE_RES_COMPLETE_HEADER) {
+        return;
+    }
+
+    char *auth_header = client->auth_header;
+    if (auth_header) {
+        http_utils_trim_whitespace(&auth_header);
+        ESP_LOGD(TAG, "UNAUTHORIZED: %s", auth_header);
+        client->redirect_counter++;
+        if (http_utils_str_starts_with(auth_header, "Digest") == 0) {
+            ESP_LOGD(TAG, "type = Digest");
+            client->connection_info.auth_type = HTTP_AUTH_TYPE_DIGEST;
+#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
+        } else if (http_utils_str_starts_with(auth_header, "Basic") == 0) {
+            ESP_LOGD(TAG, "type = Basic");
+            client->connection_info.auth_type = HTTP_AUTH_TYPE_BASIC;
+#endif
+        } else {
+            client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
+            ESP_LOGE(TAG, "This authentication method is not supported: %s", auth_header);
+            return;
+        }
+
+        _clear_auth_data(client);
+
+        client->auth_data->method = strdup(HTTP_METHOD_MAPPING[client->connection_info.method]);
+
+        client->auth_data->nc = 1;
+        client->auth_data->realm = http_utils_get_string_between(auth_header, "realm=\"", "\"");
+        client->auth_data->algorithm = http_utils_get_string_between(auth_header, "algorithm=", ",");
+        client->auth_data->qop = http_utils_get_string_between(auth_header, "qop=\"", "\"");
+        client->auth_data->nonce = http_utils_get_string_between(auth_header, "nonce=\"", "\"");
+        client->auth_data->opaque = http_utils_get_string_between(auth_header, "opaque=\"", "\"");
+        client->process_again = 1;
+    } else {
+        client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
+        ESP_LOGW(TAG, "This request requires authentication, but does not provide header information for that");
+    }
+}

+ 24 - 1
components/esp_http_client/include/esp_http_client.h

@@ -118,8 +118,20 @@ typedef struct {
     void                        *user_data;               /*!< HTTP user_data context */
     void                        *user_data;               /*!< HTTP user_data context */
     bool                        is_async;                 /*!< Set asynchronous mode, only supported with HTTPS for now */
     bool                        is_async;                 /*!< Set asynchronous mode, only supported with HTTPS for now */
     bool                        use_global_ca_store;      /*!< Use a global ca_store for all the connections in which this bool is set. */
     bool                        use_global_ca_store;      /*!< Use a global ca_store for all the connections in which this bool is set. */
+    bool                        skip_cert_common_name_check;    /*!< Skip any validation of server certificate CN field */
 } esp_http_client_config_t;
 } esp_http_client_config_t;
 
 
+/**
+ * Enum for the HTTP status codes.
+ */
+typedef enum {
+    /* 3xx - Redirection */
+    HttpStatus_MovedPermanently  = 301,
+    HttpStatus_Found             = 302,
+
+    /* 4xx - Client Error */
+    HttpStatus_Unauthorized      = 401
+} HttpStatus_Code;
 
 
 #define ESP_ERR_HTTP_BASE               (0x7000)                    /*!< Starting number of HTTP error codes */
 #define ESP_ERR_HTTP_BASE               (0x7000)                    /*!< Starting number of HTTP error codes */
 #define ESP_ERR_HTTP_MAX_REDIRECT       (ESP_ERR_HTTP_BASE + 1)     /*!< The error exceeds the number of HTTP redirects */
 #define ESP_ERR_HTTP_MAX_REDIRECT       (ESP_ERR_HTTP_BASE + 1)     /*!< The error exceeds the number of HTTP redirects */
@@ -441,12 +453,23 @@ esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_h
  *
  *
  * @param[in]  client  The esp_http_client handle
  * @param[in]  client  The esp_http_client handle
  *
  *
- * @return
+ * @return      
  *     - ESP_OK
  *     - ESP_OK
  *     - ESP_FAIL
  *     - ESP_FAIL
  */
  */
 esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client);
 esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client);
 
 
+/**
+ * @brief      On receiving HTTP Status code 401, this API can be invoked to add authorization
+ *             information.
+ *
+ * @note       There is a possibility of receiving body message with redirection status codes, thus make sure
+ *             to flush off body data after calling this API.
+ *
+ * @param[in]  client   The esp_http_client handle
+ */
+void esp_http_client_add_auth(esp_http_client_handle_t client);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 126 - 4
components/esp_https_ota/include/esp_https_ota.h

@@ -15,20 +15,40 @@
 #pragma once
 #pragma once
 
 
 #include <esp_http_client.h>
 #include <esp_http_client.h>
+#include <esp_ota_ops.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
+typedef void *esp_https_ota_handle_t;
+
+/**
+ * @brief ESP HTTPS OTA configuration
+ */
+typedef struct {
+    const esp_http_client_config_t *http_config;   /*!< ESP HTTP client configuration */
+} esp_https_ota_config_t;
+
+#define ESP_ERR_HTTPS_OTA_BASE            (0x9000)
+#define ESP_ERR_HTTPS_OTA_IN_PROGRESS     (ESP_ERR_HTTPS_OTA_BASE + 1)  /* OTA operation in progress */
+
 /**
 /**
  * @brief    HTTPS OTA Firmware upgrade.
  * @brief    HTTPS OTA Firmware upgrade.
  *
  *
- * This function performs HTTPS OTA Firmware upgrade
- *
+ * This function allocates HTTPS OTA Firmware upgrade context, establishes HTTPS connection, 
+ * reads image data from HTTP stream and writes it to OTA partition and 
+ * finishes HTTPS OTA Firmware upgrade operation.
+ * This API supports URL redirection, but if CA cert of URLs differ then it
+ * should be appended to `cert_pem` member of `config`.
+ * 
  * @param[in]  config       pointer to esp_http_client_config_t structure.
  * @param[in]  config       pointer to esp_http_client_config_t structure.
  *
  *
- * @note     For secure HTTPS updates, the `cert_pem` member of `config`
- *           structure must be set to the server certificate.
+ * @note     This API handles the entire OTA operation, so if this API is being used
+ *           then no other APIs from `esp_https_ota` component should be called.
+ *           If more information and control is needed during the HTTPS OTA process,
+ *           then one can use `esp_https_ota_begin` and subsequent APIs. If this API returns
+ *           successfully, esp_restart() must be called to boot from the new firmware image.
  *
  *
  * @return
  * @return
  *    - ESP_OK: OTA data updated, next reboot will use specified partition.
  *    - ESP_OK: OTA data updated, next reboot will use specified partition.
@@ -41,6 +61,108 @@ extern "C" {
  */
  */
 esp_err_t esp_https_ota(const esp_http_client_config_t *config);
 esp_err_t esp_https_ota(const esp_http_client_config_t *config);
 
 
+/**
+ * @brief    Start HTTPS OTA Firmware upgrade
+ *
+ * This function initializes ESP HTTPS OTA context and establishes HTTPS connection.
+ * This function must be invoked first. If this function returns successfully, then `esp_https_ota_perform` should be
+ * called to continue with the OTA process and there should be a call to `esp_https_ota_finish` on
+ * completion of OTA operation or on failure in subsequent operations.
+ * This API supports URL redirection, but if CA cert of URLs differ then it
+ * should be appended to `cert_pem` member of `http_config`, which is a part of `ota_config`.
+ * In case of error, this API explicitly sets `handle` to NULL.
+ *
+ * @param[in]   ota_config       pointer to esp_https_ota_config_t structure
+ * @param[out]  handle           pointer to an allocated data of type `esp_https_ota_handle_t`
+ *                               which will be initialised in this function
+ *
+ * @note     This API is blocking, so setting `is_async` member of `http_config` structure will
+ *           result in an error.
+ *
+ * @return
+ *    - ESP_OK: HTTPS OTA Firmware upgrade context initialised and HTTPS connection established
+ *    - ESP_FAIL: For generic failure.
+ *    - ESP_ERR_INVALID_ARG: Invalid argument (missing/incorrect config, certificate, etc.)
+ *    - For other return codes, refer documentation in app_update component and esp_http_client 
+ *      component in esp-idf.
+ */
+esp_err_t esp_https_ota_begin(esp_https_ota_config_t *ota_config, esp_https_ota_handle_t *handle);
+
+/**
+ * @brief    Read image data from HTTP stream and write it to OTA partition 
+ *
+ * This function reads image data from HTTP stream and writes it to OTA partition. This function 
+ * must be called only if esp_https_ota_begin() returns successfully.
+ * This function must be called in a loop since it returns after every HTTP read operation thus 
+ * giving you the flexibility to stop OTA operation midway.
+ * 
+ * @param[in]  https_ota_handle  pointer to esp_https_ota_handle_t structure
+ *
+ * @return
+ *    - ESP_ERR_HTTPS_OTA_IN_PROGRESS: OTA update is in progress, call this API again to continue.
+ *    - ESP_OK: OTA update was successful
+ *    - ESP_FAIL: OTA update failed
+ *    - ESP_ERR_INVALID_ARG: Invalid argument
+ *    - ESP_ERR_OTA_VALIDATE_FAILED: Invalid app image
+ *    - ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation.
+ *    - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed.
+ *    - For other return codes, refer OTA documentation in esp-idf's app_update component.
+ */
+esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle);
+
+/**
+ * @brief    Clean-up HTTPS OTA Firmware upgrade and close HTTPS connection
+ *
+ * This function closes the HTTP connection and frees the ESP HTTPS OTA context.
+ * This function switches the boot partition to the OTA partition containing the
+ * new firmware image.
+ *
+ * @note     If this API returns successfully, esp_restart() must be called to
+ *           boot from the new firmware image
+ *
+ * @param[in]  https_ota_handle   pointer to esp_https_ota_handle_t structure
+ *
+ * @return
+ *    - ESP_OK: Clean-up successful
+ *    - ESP_ERR_INVALID_STATE
+ *    - ESP_ERR_INVALID_ARG: Invalid argument
+ *    - ESP_ERR_OTA_VALIDATE_FAILED: Invalid app image
+ */
+esp_err_t esp_https_ota_finish(esp_https_ota_handle_t https_ota_handle);
+
+
+/**
+ * @brief   Reads app description from image header. The app description provides information
+ *          like the "Firmware version" of the image.
+ * 
+ * @note    This API can be called only after esp_https_ota_begin() and before esp_https_ota_perform().
+ *          Calling this API is not mandatory.
+ *
+ * @param[in]   https_ota_handle   pointer to esp_https_ota_handle_t structure
+ * @param[out]  new_app_info       pointer to an allocated esp_app_desc_t structure
+ * 
+ * @return 
+ *    - ESP_ERR_INVALID_ARG: Invalid arguments
+ *    - ESP_FAIL: Failed to read image descriptor
+ *    - ESP_OK: Successfully read image descriptor
+ */
+esp_err_t esp_https_ota_get_img_desc(esp_https_ota_handle_t https_ota_handle, esp_app_desc_t *new_app_info);
+
+
+/*
+* @brief  This function returns OTA image data read so far.
+*
+* @note   This API should be called only if `esp_https_ota_perform()` has been called atleast once or
+*         if `esp_https_ota_get_img_desc` has been called before.
+*
+* @param[in]   https_ota_handle   pointer to esp_https_ota_handle_t structure
+*
+* @return
+*    - -1    On failure
+*    - total bytes read so far
+*/
+int esp_https_ota_get_image_len_read(esp_https_ota_handle_t https_ota_handle);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 302 - 74
components/esp_https_ota/src/esp_https_ota.c

@@ -16,121 +16,349 @@
 #include <stdlib.h>
 #include <stdlib.h>
 #include <string.h>
 #include <string.h>
 #include <esp_https_ota.h>
 #include <esp_https_ota.h>
-#include <esp_ota_ops.h>
 #include <esp_log.h>
 #include <esp_log.h>
 
 
-#define DEFAULT_OTA_BUF_SIZE 256
+#define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1
+#define DEFAULT_OTA_BUF_SIZE IMAGE_HEADER_SIZE
 static const char *TAG = "esp_https_ota";
 static const char *TAG = "esp_https_ota";
 
 
-static void http_cleanup(esp_http_client_handle_t client)
+typedef enum {
+    ESP_HTTPS_OTA_INIT,
+    ESP_HTTPS_OTA_BEGIN,
+    ESP_HTTPS_OTA_IN_PROGRESS,
+    ESP_HTTPS_OTA_SUCCESS,
+} esp_https_ota_state;
+
+struct esp_https_ota_handle {
+    esp_ota_handle_t update_handle;
+    const esp_partition_t *update_partition;
+    esp_http_client_handle_t http_client;
+    char *ota_upgrade_buf;
+    size_t ota_upgrade_buf_size;
+    int binary_file_len;
+    esp_https_ota_state state;
+};
+
+typedef struct esp_https_ota_handle esp_https_ota_t;
+
+static bool process_again(int status_code)
+{
+    switch (status_code) {
+        case HttpStatus_MovedPermanently:
+        case HttpStatus_Found:
+        case HttpStatus_Unauthorized:
+            return true;
+        default:
+            return false;
+    }
+    return false;
+}
+
+static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client, int status_code)
+{
+    esp_err_t err;
+    if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found) {
+        err = esp_http_client_set_redirection(http_client);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "URL redirection Failed");
+            return err;
+        }
+    } else if (status_code == HttpStatus_Unauthorized) {
+        esp_http_client_add_auth(http_client);
+    }
+    
+    char upgrade_data_buf[DEFAULT_OTA_BUF_SIZE];
+    if (process_again(status_code)) {
+        while (1) {
+            int data_read = esp_http_client_read(http_client, upgrade_data_buf, DEFAULT_OTA_BUF_SIZE);
+            if (data_read < 0) {
+                ESP_LOGE(TAG, "Error: SSL data read error");
+                return ESP_FAIL;
+            } else if (data_read == 0) {
+                return ESP_OK;
+            }
+        }
+    }
+    return ESP_OK;
+}
+
+static esp_err_t _http_connect(esp_http_client_handle_t http_client)
+{
+    esp_err_t err = ESP_FAIL;
+    int status_code;
+    do {
+        err = esp_http_client_open(http_client, 0);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
+            return err;
+        }
+        esp_http_client_fetch_headers(http_client);
+        status_code = esp_http_client_get_status_code(http_client);
+        if (_http_handle_response_code(http_client, status_code) != ESP_OK) {
+            return ESP_FAIL;
+        }
+    } while (process_again(status_code));
+    return err;
+}
+
+static void _http_cleanup(esp_http_client_handle_t client)
 {
 {
     esp_http_client_close(client);
     esp_http_client_close(client);
     esp_http_client_cleanup(client);
     esp_http_client_cleanup(client);
 }
 }
 
 
-esp_err_t esp_https_ota(const esp_http_client_config_t *config)
+static esp_err_t _ota_write(esp_https_ota_t *https_ota_handle, const void *buffer, size_t buf_len)
 {
 {
-    if (!config) {
-        ESP_LOGE(TAG, "esp_http_client config not found");
+    if (buffer == NULL || https_ota_handle == NULL) {
+        return ESP_FAIL;
+    }
+    esp_err_t err = esp_ota_write(https_ota_handle->update_handle, buffer, buf_len);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%d", err);
+    } else {
+        https_ota_handle->binary_file_len += buf_len;
+        ESP_LOGD(TAG, "Written image length %d", https_ota_handle->binary_file_len);
+        err = ESP_ERR_HTTPS_OTA_IN_PROGRESS;
+    }
+    return err;
+}
+
+esp_err_t esp_https_ota_begin(esp_https_ota_config_t *ota_config, esp_https_ota_handle_t *handle)
+{
+    esp_err_t err;
+
+    if (handle == NULL || ota_config == NULL || ota_config->http_config == NULL) {
+        ESP_LOGE(TAG, "esp_https_ota_begin: Invalid argument");
+        if (handle) {
+            *handle = NULL;
+        }
         return ESP_ERR_INVALID_ARG;
         return ESP_ERR_INVALID_ARG;
     }
     }
 
 
 #if !CONFIG_OTA_ALLOW_HTTP
 #if !CONFIG_OTA_ALLOW_HTTP
-    if (!config->cert_pem && !config->use_global_ca_store) {
-        ESP_LOGE(TAG, "Server certificate not found, either through configuration or global CA store");
+    if (!ota_config->http_config->cert_pem) {
+        ESP_LOGE(TAG, "Server certificate not found in esp_http_client config");
+        *handle = NULL;
         return ESP_ERR_INVALID_ARG;
         return ESP_ERR_INVALID_ARG;
     }
     }
 #endif
 #endif
 
 
-    esp_http_client_handle_t client = esp_http_client_init(config);
-    if (client == NULL) {
+    esp_https_ota_t *https_ota_handle = calloc(1, sizeof(esp_https_ota_t));
+    if (!https_ota_handle) {
+        ESP_LOGE(TAG, "Couldn't allocate memory to upgrade data buffer");
+        *handle = NULL;
+        return ESP_ERR_NO_MEM;
+    }
+    
+    /* Initiate HTTP Connection */
+    https_ota_handle->http_client = esp_http_client_init(ota_config->http_config);
+    if (https_ota_handle->http_client == NULL) {
         ESP_LOGE(TAG, "Failed to initialise HTTP connection");
         ESP_LOGE(TAG, "Failed to initialise HTTP connection");
-        return ESP_FAIL;
+        err = ESP_FAIL;
+        goto failure;
     }
     }
 
 
-#if !CONFIG_OTA_ALLOW_HTTP
-    if (esp_http_client_get_transport_type(client) != HTTP_TRANSPORT_OVER_SSL) {
-        ESP_LOGE(TAG, "Transport is not over HTTPS");
-        return ESP_FAIL;
-    }
-#endif
-
-    esp_err_t err = esp_http_client_open(client, 0);
+    err = _http_connect(https_ota_handle->http_client);
     if (err != ESP_OK) {
     if (err != ESP_OK) {
-        esp_http_client_cleanup(client);
-        ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
-        return err;
+        ESP_LOGE(TAG, "Failed to establish HTTP connection");
+        goto http_cleanup;
     }
     }
-    esp_http_client_fetch_headers(client);
 
 
-    esp_ota_handle_t update_handle = 0;
-    const esp_partition_t *update_partition = NULL;
+    https_ota_handle->update_partition = NULL;
     ESP_LOGI(TAG, "Starting OTA...");
     ESP_LOGI(TAG, "Starting OTA...");
-    update_partition = esp_ota_get_next_update_partition(NULL);
-    if (update_partition == NULL) {
+    https_ota_handle->update_partition = esp_ota_get_next_update_partition(NULL);
+    if (https_ota_handle->update_partition == NULL) {
         ESP_LOGE(TAG, "Passive OTA partition not found");
         ESP_LOGE(TAG, "Passive OTA partition not found");
-        http_cleanup(client);
-        return ESP_FAIL;
+        err = ESP_FAIL;
+        goto http_cleanup;
     }
     }
     ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
     ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
-             update_partition->subtype, update_partition->address);
+        https_ota_handle->update_partition->subtype, https_ota_handle->update_partition->address);
 
 
-    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);
-        http_cleanup(client);
-        return err;
+    const int alloc_size = (ota_config->http_config->buffer_size > DEFAULT_OTA_BUF_SIZE) ?
+                            ota_config->http_config->buffer_size : DEFAULT_OTA_BUF_SIZE;
+    https_ota_handle->ota_upgrade_buf = (char *)malloc(alloc_size);
+    if (!https_ota_handle->ota_upgrade_buf) {
+        ESP_LOGE(TAG, "Couldn't allocate memory to upgrade data buffer");
+        err = ESP_ERR_NO_MEM;
+        goto http_cleanup;
     }
     }
-    ESP_LOGI(TAG, "esp_ota_begin succeeded");
-    ESP_LOGI(TAG, "Please Wait. This may take time");
+    https_ota_handle->ota_upgrade_buf_size = alloc_size;
 
 
-    esp_err_t ota_write_err = ESP_OK;
-    const int alloc_size = (config->buffer_size > 0) ? config->buffer_size : DEFAULT_OTA_BUF_SIZE;
-    char *upgrade_data_buf = (char *)malloc(alloc_size);
-    if (!upgrade_data_buf) {
-        ESP_LOGE(TAG, "Couldn't allocate memory to upgrade data buffer");
-        return ESP_ERR_NO_MEM;
+    https_ota_handle->binary_file_len = 0;
+    *handle = (esp_https_ota_handle_t)https_ota_handle;
+    https_ota_handle->state = ESP_HTTPS_OTA_BEGIN;
+    return ESP_OK;
+
+http_cleanup:
+    _http_cleanup(https_ota_handle->http_client);
+failure:
+    free(https_ota_handle);
+    *handle = NULL;
+    return err;
+}
+
+esp_err_t esp_https_ota_get_img_desc(esp_https_ota_handle_t https_ota_handle, esp_app_desc_t *new_app_info)
+{
+    esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
+    if (handle == NULL || new_app_info == NULL)  {
+        ESP_LOGE(TAG, "esp_https_ota_read_img_desc: Invalid argument");
+        return ESP_ERR_INVALID_ARG;
     }
     }
+    if (handle->state < ESP_HTTPS_OTA_BEGIN) {
+        ESP_LOGE(TAG, "esp_https_ota_read_img_desc: Invalid state");
+        return ESP_FAIL;
+    }
+    int data_read_size = IMAGE_HEADER_SIZE;
+    int data_read = esp_http_client_read(handle->http_client,
+                                         handle->ota_upgrade_buf,
+                                         data_read_size);
+    if (data_read < 0) {
+        return ESP_FAIL;
+    }
+    if (data_read >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
+        memcpy(new_app_info, &handle->ota_upgrade_buf[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
+        handle->binary_file_len += data_read;
+    } else {
+        return ESP_FAIL;
+    }
+    return ESP_OK;                                
+}
 
 
-    int binary_file_len = 0;
-    while (1) {
-        int data_read = esp_http_client_read(client, upgrade_data_buf, alloc_size);
-        if (data_read == 0) {
-            ESP_LOGI(TAG, "Connection closed, all data received");
+esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
+{
+    esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
+    if (handle == NULL) {
+        ESP_LOGE(TAG, "esp_https_ota_perform: Invalid argument");
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (handle->state < ESP_HTTPS_OTA_BEGIN) {
+        ESP_LOGE(TAG, "esp_https_ota_perform: Invalid state");
+        return ESP_FAIL;
+    }
+
+    esp_err_t err;
+    int data_read;
+    switch (handle->state) {
+        case ESP_HTTPS_OTA_BEGIN:
+            err = esp_ota_begin(handle->update_partition, OTA_SIZE_UNKNOWN, &handle->update_handle);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
+                return err;
+            }
+            handle->state = ESP_HTTPS_OTA_IN_PROGRESS;
+            /* In case `esp_https_ota_read_img_desc` was invoked first,
+               then the image data read there should be written to OTA partition
+               */
+            if (handle->binary_file_len) {
+                return _ota_write(handle, (const void *)handle->ota_upgrade_buf, handle->binary_file_len);
+            }
+            /* falls through */
+        case ESP_HTTPS_OTA_IN_PROGRESS:
+            data_read = esp_http_client_read(handle->http_client,
+                                             handle->ota_upgrade_buf,
+                                             handle->ota_upgrade_buf_size);
+            if (data_read == 0) {
+                ESP_LOGI(TAG, "Connection closed, all data received");
+            } else if (data_read < 0) {
+                ESP_LOGE(TAG, "Error: SSL data read error");
+                return ESP_FAIL;
+            } else if (data_read > 0) {
+                return _ota_write(handle, (const void *)handle->ota_upgrade_buf, data_read);
+            }
+            handle->state = ESP_HTTPS_OTA_SUCCESS;
             break;
             break;
-        }
-        if (data_read < 0) {
-            ESP_LOGE(TAG, "Error: SSL data read error");
+         default:
+            ESP_LOGE(TAG, "Invalid ESP HTTPS OTA State");
+            return ESP_FAIL;
             break;
             break;
-        }
-        if (data_read > 0) {
-            ota_write_err = esp_ota_write(update_handle, (const void *) upgrade_data_buf, data_read);
-            if (ota_write_err != ESP_OK) {
-                break;
+    }
+    return ESP_OK;
+}
+
+esp_err_t esp_https_ota_finish(esp_https_ota_handle_t https_ota_handle)
+{
+    esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
+    if (handle == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (handle->state < ESP_HTTPS_OTA_BEGIN) {
+        return ESP_FAIL;
+    }
+
+    esp_err_t err = ESP_OK;
+    switch (handle->state) {
+        case ESP_HTTPS_OTA_SUCCESS:
+        case ESP_HTTPS_OTA_IN_PROGRESS:
+            err = esp_ota_end(handle->update_handle);
+            /* falls through */
+        case ESP_HTTPS_OTA_BEGIN:
+            if (handle->ota_upgrade_buf) {
+                free(handle->ota_upgrade_buf);
             }
             }
-            binary_file_len += data_read;
-            ESP_LOGD(TAG, "Written image length %d", binary_file_len);
+            if (handle->http_client) {
+                _http_cleanup(handle->http_client);
+            }
+            break;
+        default:
+            ESP_LOGE(TAG, "Invalid ESP HTTPS OTA State");
+            break;
+    }
+
+    if ((err == ESP_OK) && (handle->state == ESP_HTTPS_OTA_SUCCESS)) {
+        esp_err_t err = esp_ota_set_boot_partition(handle->update_partition);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%d", err);
         }
         }
     }
     }
-    free(upgrade_data_buf);
-    http_cleanup(client); 
-    ESP_LOGD(TAG, "Total binary data length writen: %d", binary_file_len);
-    
-    esp_err_t ota_end_err = esp_ota_end(update_handle);
-    if (ota_write_err != ESP_OK) {
-        ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%d", err);
-        return ota_write_err;
-    } else if (ota_end_err != ESP_OK) {
-        ESP_LOGE(TAG, "Error: esp_ota_end failed! err=0x%d. Image is invalid", ota_end_err);
-        return ota_end_err;
+    free(handle);
+    return err;
+}
+
+int esp_https_ota_get_image_len_read(esp_https_ota_handle_t https_ota_handle)
+{
+    esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
+    if (handle == NULL) {
+        return -1;
     }
     }
+    if (handle->state < ESP_HTTPS_OTA_IN_PROGRESS) {
+        return -1;
+    }
+    return handle->binary_file_len;
+}
 
 
-    err = esp_ota_set_boot_partition(update_partition);
+esp_err_t esp_https_ota(const esp_http_client_config_t *config)
+{
+    if (!config) {
+        ESP_LOGE(TAG, "esp_http_client config not found");
+        return ESP_ERR_INVALID_ARG;
+    }    
+
+    esp_https_ota_config_t ota_config = {
+        .http_config = config,
+    };
+
+    esp_https_ota_handle_t https_ota_handle = NULL;
+    esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
+    if (https_ota_handle == NULL) {
+        return ESP_FAIL;
+    }
+
+    while (1) {
+        err = esp_https_ota_perform(https_ota_handle);
+        if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
+            break;
+        }
+    }
+
+    esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle);
     if (err != ESP_OK) {
     if (err != ESP_OK) {
-        ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%d", err);
+        /* If there was an error in esp_https_ota_perform(),
+           then it is given more precedence than error in esp_https_ota_finish()
+         */
         return err;
         return err;
+    } else if (ota_finish_err != ESP_OK) {
+        return ota_finish_err;
     }
     }
-    ESP_LOGI(TAG, "esp_ota_set_boot_partition succeeded"); 
-
     return ESP_OK;
     return ESP_OK;
-}
+}

+ 9 - 0
components/tcp_transport/include/esp_transport_ssl.h

@@ -69,6 +69,15 @@ void esp_transport_ssl_set_client_cert_data(esp_transport_handle_t t, const char
  */
  */
 void esp_transport_ssl_set_client_key_data(esp_transport_handle_t t, const char *data, int len);
 void esp_transport_ssl_set_client_key_data(esp_transport_handle_t t, const char *data, int len);
 
 
+/**
+ * @brief      Skip validation of certificate's common name field
+ *
+ * @note       Skipping CN validation is not recommended
+ *
+ * @param      t     ssl transport
+ */
+void esp_transport_ssl_skip_common_name_check(esp_transport_handle_t t);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 8 - 0
components/tcp_transport/transport_ssl.c

@@ -190,6 +190,14 @@ void esp_transport_ssl_set_client_key_data(esp_transport_handle_t t, const char
     }
     }
 }
 }
 
 
+void esp_transport_ssl_skip_common_name_check(esp_transport_handle_t t)
+{
+    transport_ssl_t *ssl = esp_transport_get_context_data(t);
+    if (t && ssl) {
+        ssl->cfg.skip_common_name = true;
+    }
+}
+
 esp_transport_handle_t esp_transport_ssl_init()
 esp_transport_handle_t esp_transport_ssl_init()
 {
 {
     esp_transport_handle_t t = esp_transport_init();
     esp_transport_handle_t t = esp_transport_init();

+ 6 - 0
examples/system/ota/advanced_https_ota/CMakeLists.txt

@@ -0,0 +1,6 @@
+# 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)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(advanced_https_ota)

+ 9 - 0
examples/system/ota/advanced_https_ota/Makefile

@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := advanced_https_ota
+
+include $(IDF_PATH)/make/project.mk
+

+ 8 - 0
examples/system/ota/advanced_https_ota/main/CMakeLists.txt

@@ -0,0 +1,8 @@
+set(COMPONENT_SRCS "advanced_https_ota_example.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+
+# Embed the server root certificate into the final binary
+set(COMPONENT_EMBED_TXTFILES ${IDF_PROJECT_PATH}/server_certs/ca_cert.pem)
+
+register_component()

+ 21 - 0
examples/system/ota/advanced_https_ota/main/Kconfig.projbuild

@@ -0,0 +1,21 @@
+menu "Example Configuration"
+
+    config WIFI_SSID
+        string "WiFi SSID"
+        default "myssid"
+        help
+            SSID (network name) for the example to connect to.
+
+    config WIFI_PASSWORD
+        string "WiFi Password"
+        default "mypassword"
+        help
+            WiFi password (WPA or WPA2) for the example to use.
+
+    config FIRMWARE_UPGRADE_URL
+        string "firmware upgrade url endpoint"
+        default "https://192.168.0.3:8070/hello-world.bin"
+        help
+            URL of server which hosts the firmware
+            image.
+endmenu

+ 177 - 0
examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c

@@ -0,0 +1,177 @@
+/* Advanced HTTPS OTA 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 "string.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_event_loop.h"
+#include "esp_log.h"
+#include "esp_ota_ops.h"
+#include "esp_http_client.h"
+#include "esp_https_ota.h"
+
+#include "nvs.h"
+#include "nvs_flash.h"
+
+static const char *TAG = "advanced_https_ota_example";
+extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
+extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
+
+/* FreeRTOS event group to signal when we are connected & ready to make a request */
+static EventGroupHandle_t wifi_event_group;
+
+/* The event group allows multiple bits for each event,
+   but we only care about one event - are we connected
+   to the AP with an IP? */
+const int CONNECTED_BIT = BIT0;
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+    switch (event->event_id) {
+    case SYSTEM_EVENT_STA_START:
+        esp_wifi_connect();
+        break;
+    case SYSTEM_EVENT_STA_GOT_IP:
+        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
+        break;
+    case SYSTEM_EVENT_STA_DISCONNECTED:
+        /* This is a workaround as ESP32 WiFi libs don't currently
+           auto-reassociate. */
+        esp_wifi_connect();
+        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
+        break;
+    default:
+        break;
+    }
+    return ESP_OK;
+}
+
+static void initialise_wifi(void)
+{
+    tcpip_adapter_init();
+    wifi_event_group = xEventGroupCreate();
+    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
+    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = CONFIG_WIFI_SSID,
+            .password = CONFIG_WIFI_PASSWORD,
+        },
+    };
+    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s", wifi_config.sta.ssid);
+    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
+    ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
+    ESP_ERROR_CHECK( esp_wifi_start() );
+}
+
+static esp_err_t validate_image_header(esp_app_desc_t *new_app_info)
+{
+    if (new_app_info == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    const esp_partition_t *running = esp_ota_get_running_partition();
+    esp_app_desc_t running_app_info;
+    if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
+        ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
+    }
+
+    if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) {
+        ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
+
+void advanced_ota_example_task(void * pvParameter)
+{
+    ESP_LOGI(TAG, "Starting Advanced OTA example");
+
+    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
+                        false, true, portMAX_DELAY);
+    ESP_LOGI(TAG, "Connected to WiFi network! Attempting to connect to server...");
+    
+    esp_err_t ota_finish_err = ESP_OK;
+    esp_http_client_config_t config = {
+        .url = CONFIG_FIRMWARE_UPGRADE_URL,
+        .cert_pem = (char *)server_cert_pem_start,
+    };
+    
+    esp_https_ota_config_t ota_config = {
+        .http_config = &config,
+    };
+    
+    esp_https_ota_handle_t https_ota_handle = NULL;
+    esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed");
+        vTaskDelete(NULL);
+    }
+
+    esp_app_desc_t app_desc;
+    err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed");
+        goto ota_end;
+    }
+    err = validate_image_header(&app_desc);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "image header verification failed");
+        goto ota_end;
+    }
+
+    while (1) {
+        err = esp_https_ota_perform(https_ota_handle);
+        if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
+            break;
+        }
+        // esp_https_ota_perform returns after every read operation which gives user the ability to
+        // monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image
+        // data read so far.
+        ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
+    }
+
+ota_end:
+    ota_finish_err = esp_https_ota_finish(https_ota_handle);
+    if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) {
+        ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ...");
+        vTaskDelay(1000 / portTICK_PERIOD_MS);
+        esp_restart();
+    } else {
+        ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed...");
+    }
+
+    while (1) {
+        vTaskDelay(1000 / portTICK_PERIOD_MS);
+    }
+}
+
+void app_main()
+{
+    // Initialize NVS.
+    esp_err_t err = nvs_flash_init();
+    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+        // 1.OTA app partition table has a smaller NVS partition size than the non-OTA
+        // partition table. This size mismatch may cause NVS initialization to fail.
+        // 2.NVS partition contains data in new format and cannot be recognized by this version of code.
+        // If this happens, we erase NVS partition and initialize NVS again.
+        ESP_ERROR_CHECK(nvs_flash_erase());
+        err = nvs_flash_init();
+    }
+    ESP_ERROR_CHECK( err );
+
+    initialise_wifi();
+    xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 5, NULL);
+}
+

+ 6 - 0
examples/system/ota/advanced_https_ota/main/component.mk

@@ -0,0 +1,6 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
+COMPONENT_EMBED_TXTFILES :=  ${PROJECT_PATH}/server_certs/ca_cert.pem

+ 0 - 0
examples/system/ota/advanced_https_ota/server_certs/ca_cert.pem


+ 146 - 0
examples/system/ota/simple_ota_example/example_test.py

@@ -0,0 +1,146 @@
+import re
+import os
+import sys
+import socket
+import BaseHTTPServer
+import SimpleHTTPServer
+from threading import Thread
+import ssl
+
+try:
+    import IDF
+except ImportError:
+    # this is a test case write with tiny-test-fw.
+    # to run test cases outside tiny-test-fw,
+    # we need to set environment variable `TEST_FW_PATH`,
+    # then get and insert `TEST_FW_PATH` to sys path before import FW module
+    test_fw_path = os.getenv("TEST_FW_PATH")
+    if test_fw_path and test_fw_path not in sys.path:
+        sys.path.insert(0, test_fw_path)
+    import IDF
+
+import DUT
+
+server_cert = "-----BEGIN CERTIFICATE-----\n" \
+              "MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
+              "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"\
+              "aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF\n"\
+              "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"\
+              "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"\
+              "CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0\n"\
+              "nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC\n"\
+              "9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA\n"\
+              "w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF\n"\
+              "3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M\n"\
+              "lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY\n"\
+              "IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww\n"\
+              "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd\n"\
+              "/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA\n"\
+              "lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl\n"\
+              "6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2\n"\
+              "fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu\n"\
+              "y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy\n"\
+              "hA==\n"\
+              "-----END CERTIFICATE-----\n"
+
+server_key = "-----BEGIN PRIVATE KEY-----\n"\
+             "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXN8LK/eYi/tOU\n"\
+             "uQ5vG6cp8J2sn/OB0A2uzHREG2kQ+FXnrSFYnR8SKfScg4yklKIX0TpCw92vpD56\n"\
+             "iAfhec4xT0kT6Ibvjc3V098YTMm9GXJy38KTxKzAo8L35Veyc51mZTD3L/G0A1tR\n"\
+             "ED9Oym8/PPcC+/fzZ999+skaHuig6ZplIJkWVJgctkDD9eVGvSxJFsukUZjSBeNo\n"\
+             "BXyIceI8NgvLiRk56mNX1TnXGL4gawvjDvnO4yCwfIXecl4ZgeC0ZUGuQvRkobm5\n"\
+             "1jTBwGPKyO5sMkLiJKU2KrYcPd+GzuPoJl11Xa/zjkyVUo3E/SQ7hSPgPyv7lRJY\n"\
+             "Lwkp8DDFAgMBAAECggEAfBhAfQE7mUByNbxgAgI5fot9eaqR1Nf+QpJ6X2H3KPwC\n"\
+             "02sa0HOwieFwYfj6tB1doBoNq7i89mTc+QUlIn4pHgIowHO0OGawomeKz5BEhjCZ\n"\
+             "4XeLYGSoODary2+kNkf2xY8JTfFEcyvGBpJEwc4S2VyYgRRx+IgnumTSH+N5mIKZ\n"\
+             "SXWNdZIuHEmkwod+rPRXs6/r+PH0eVW6WfpINEbr4zVAGXJx2zXQwd2cuV1GTJWh\n"\
+             "cPVOXLu+XJ9im9B370cYN6GqUnR3fui13urYbnWnEf3syvoH/zuZkyrVChauoFf8\n"\
+             "8EGb74/HhXK7Q2s8NRakx2c7OxQifCbcy03liUMmyQKBgQDFAob5B/66N4Q2cq/N\n"\
+             "MWPf98kYBYoLaeEOhEJhLQlKk0pIFCTmtpmUbpoEes2kCUbH7RwczpYko8tlKyoB\n"\
+             "6Fn6RY4zQQ64KZJI6kQVsjkYpcP/ihnOY6rbds+3yyv+4uPX7Eh9sYZwZMggE19M\n"\
+             "CkFHkwAjiwqhiiSlUxe20sWmowKBgQDEfx4lxuFzA1PBPeZKGVBTxYPQf+DSLCre\n"\
+             "ZFg3ZmrxbCjRq1O7Lra4FXWD3dmRq7NDk79JofoW50yD8wD7I0B7opdDfXD2idO8\n"\
+             "0dBnWUKDr2CAXyoLEINce9kJPbx4kFBQRN9PiGF7VkDQxeQ3kfS8CvcErpTKCOdy\n"\
+             "5wOwBTwJdwKBgDiTFTeGeDv5nVoVbS67tDao7XKchJvqd9q3WGiXikeELJyuTDqE\n"\
+             "zW22pTwMF+m3UEAxcxVCrhMvhkUzNAkANHaOatuFHzj7lyqhO5QPbh4J3FMR0X9X\n"\
+             "V8VWRSg+jA/SECP9koOl6zlzd5Tee0tW1pA7QpryXscs6IEhb3ns5R2JAoGAIkzO\n"\
+             "RmnhEOKTzDex611f2D+yMsMfy5BKK2f4vjLymBH5TiBKDXKqEpgsW0huoi8Gq9Uu\n"\
+             "nvvXXAgkIyRYF36f0vUe0nkjLuYAQAWgC2pZYgNLJR13iVbol0xHJoXQUHtgiaJ8\n"\
+             "GLYFzjHQPqFMpSalQe3oELko39uOC1CoJCHFySECgYBeycUnRBikCO2n8DNhY4Eg\n"\
+             "9Y3oxcssRt6ea5BZwgW2eAYi7/XqKkmxoSoOykUt3MJx9+EkkrL17bxFSpkj1tvL\n"\
+             "qvxn7egtsKjjgGNAxwXC4MwCvhveyUQQxtQb8AqGrGqo4jEEN0L15cnP38i2x1Uo\n"\
+             "muhfskWf4MABV0yTUaKcGg==\n"\
+             "-----END PRIVATE KEY-----\n"
+
+
+def get_my_ip():
+    s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    s1.connect(("8.8.8.8", 80))
+    my_ip = s1.getsockname()[0]
+    s1.close()
+    return my_ip
+
+
+def start_https_server(ota_image_dir, server_ip, server_port):
+    # parser = argparse.ArgumentParser()
+    # parser.add_argument('-p', '--port', dest='port', type= int,
+    #     help= "Server Port", default= 8000)
+    # args = parser.parse_args()
+    os.chdir(ota_image_dir)
+
+    server_file = os.path.join(ota_image_dir, "server_cert.pem")
+    cert_file_handle = open(server_file, "w+")
+    cert_file_handle.write(server_cert)
+    cert_file_handle.close()
+
+    key_file = os.path.join(ota_image_dir, "server_key.pem")
+    key_file_handle = open("server_key.pem", "w+")
+    key_file_handle.write(server_key)
+    key_file_handle.close()
+
+    httpd = BaseHTTPServer.HTTPServer((server_ip, server_port),
+                                      SimpleHTTPServer.SimpleHTTPRequestHandler)
+
+    httpd.socket = ssl.wrap_socket(httpd.socket,
+                                   keyfile=key_file,
+                                   certfile=server_file, server_side=True)
+    httpd.serve_forever()
+
+
+@IDF.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_simple_ota_example(env, extra_data):
+    """
+    steps: |
+      1. join AP
+      2. Fetch OTA image over HTTPS
+      3. Reboot with the new OTA image
+    """
+    dut1 = env.get_dut("simple_ota_example", "examples/system/ota/simple_ota_example")
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, "simple_ota.bin")
+    bin_size = os.path.getsize(binary_file)
+    IDF.log_performance("simple_ota_bin_size", "{}KB".format(bin_size // 1024))
+    IDF.check_performance("simple_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, 8000))
+    thread1.daemon = True
+    thread1.start()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset 0x10000", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+        thread1.close()
+    dut1.expect("Starting OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":8000/simple_ota.bin"))
+    dut1.write("https://" + host_ip + ":8000/simple_ota.bin")
+    dut1.expect("Loaded app from partition at offset 0x110000", timeout=60)
+    dut1.expect("Starting OTA example", timeout=30)
+
+
+if __name__ == '__main__':
+    test_examples_protocol_simple_ota_example()

+ 12 - 1
examples/system/ota/simple_ota_example/main/Kconfig.projbuild

@@ -12,10 +12,21 @@ menu "Example Configuration"
         help
         help
             WiFi password (WPA or WPA2) for the example to use.
             WiFi password (WPA or WPA2) for the example to use.
 
 
-    config FIRMWARE_UPGRADE_URL
+    config EXAMPLE_FIRMWARE_UPGRADE_URL
         string "firmware upgrade url endpoint"
         string "firmware upgrade url endpoint"
         default "https://192.168.0.3:8070/hello-world.bin"
         default "https://192.168.0.3:8070/hello-world.bin"
         help
         help
             URL of server which hosts the firmware
             URL of server which hosts the firmware
             image.
             image.
+
+    config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+        bool
+        default y if EXAMPLE_FIRMWARE_UPGRADE_URL = "FROM_STDIN"
+
+    config EXAMPLE_SKIP_COMMON_NAME_CHECK
+        bool "Skip server certificate CN fieldcheck"
+        default n
+        help
+            This allows you to skip the validation of OTA server certificate CN field.
+
 endmenu
 endmenu

+ 46 - 3
examples/system/ota/simple_ota_example/main/simple_ota_example.c

@@ -20,6 +20,12 @@
 
 
 #include "nvs.h"
 #include "nvs.h"
 #include "nvs_flash.h"
 #include "nvs_flash.h"
+#include "string.h"
+
+#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+#include "esp_vfs_dev.h"
+#include "driver/uart.h"
+#endif
 
 
 static const char *TAG = "simple_ota_example";
 static const char *TAG = "simple_ota_example";
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
@@ -33,6 +39,25 @@ static EventGroupHandle_t wifi_event_group;
    to the AP with an IP? */
    to the AP with an IP? */
 const int CONNECTED_BIT = BIT0;
 const int CONNECTED_BIT = BIT0;
 
 
+#define OTA_URL_SIZE 256 
+
+#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+static esp_err_t example_configure_stdin_stdout(void)
+{
+    // Initialize VFS & UART so we can use std::cout/cin
+    setvbuf(stdin, NULL, _IONBF, 0);
+    setvbuf(stdout, NULL, _IONBF, 0);
+    /* Install UART driver for interrupt-driven reads and writes */
+    ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM,
+            256, 0, 0, NULL, 0) );
+    /* Tell VFS to use UART driver */
+    esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM);
+    esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
+    /* Move the caret to the beginning of the next line on '\n' */
+    esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
+    return ESP_OK;
+}
+#endif
 esp_err_t _http_event_handler(esp_http_client_event_t *evt)
 esp_err_t _http_event_handler(esp_http_client_event_t *evt)
 {
 {
     switch(evt->event_id) {
     switch(evt->event_id) {
@@ -104,20 +129,38 @@ static void initialise_wifi(void)
 
 
 void simple_ota_example_task(void * pvParameter)
 void simple_ota_example_task(void * pvParameter)
 {
 {
-    ESP_LOGI(TAG, "Starting OTA example");
-
     /* Wait for the callback to set the CONNECTED_BIT in the
     /* Wait for the callback to set the CONNECTED_BIT in the
        event group.
        event group.
     */
     */
     xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
     xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
                         false, true, portMAX_DELAY);
                         false, true, portMAX_DELAY);
+    ESP_LOGI(TAG, "Starting OTA example");
     ESP_LOGI(TAG, "Connected to WiFi network! Attempting to connect to server...");
     ESP_LOGI(TAG, "Connected to WiFi network! Attempting to connect to server...");
     
     
     esp_http_client_config_t config = {
     esp_http_client_config_t config = {
-        .url = CONFIG_FIRMWARE_UPGRADE_URL,
+        .url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
         .cert_pem = (char *)server_cert_pem_start,
         .cert_pem = (char *)server_cert_pem_start,
         .event_handler = _http_event_handler,
         .event_handler = _http_event_handler,
     };
     };
+
+#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+    char url_buf[OTA_URL_SIZE];
+    if (strcmp(config.url, "FROM_STDIN") == 0) {
+        example_configure_stdin_stdout();
+        fgets(url_buf, OTA_URL_SIZE, stdin);
+        int len = strlen(url_buf);
+        url_buf[len - 1] = '\0';
+        config.url = url_buf;
+    } else {
+        ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
+        abort();
+    }
+#endif
+
+#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
+    config.skip_cert_common_name_check = true;
+#endif
+
     esp_err_t ret = esp_https_ota(&config);
     esp_err_t ret = esp_https_ota(&config);
     if (ret == ESP_OK) {
     if (ret == ESP_OK) {
         esp_restart();
         esp_restart();

+ 2 - 0
examples/system/ota/simple_ota_example/sdkconfig.ci

@@ -0,0 +1,2 @@
+CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN"
+CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y

+ 21 - 0
examples/system/ota/simple_ota_example/server_certs/ca_cert.pem

@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0
+nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC
+9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA
+w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF
+3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M
+lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY
+IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd
+/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA
+lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl
+6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2
+fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu
+y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy
+hA==
+-----END CERTIFICATE-----