diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index b063a6002f4..236656127a5 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -84,6 +84,7 @@ static const char *TAG = "esp-tls"; #define _esp_tls_get_global_ca_store esp_mbedtls_get_global_ca_store #define _esp_tls_free_global_ca_store esp_mbedtls_free_global_ca_store #define _esp_tls_get_ciphersuites_list esp_mbedtls_get_ciphersuites_list +#define _esp_tls_get_supported_groups_list esp_mbedtls_get_supported_groups_list #elif CONFIG_ESP_TLS_CUSTOM_STACK #define _esp_create_ssl_handle esp_tls_custom_stack_create_ssl_handle #define _esp_tls_handshake esp_tls_custom_stack_handshake @@ -106,6 +107,7 @@ static const char *TAG = "esp-tls"; #define _esp_tls_get_global_ca_store esp_tls_custom_stack_get_global_ca_store #define _esp_tls_free_global_ca_store esp_tls_custom_stack_free_global_ca_store #define _esp_tls_get_ciphersuites_list esp_tls_custom_stack_get_ciphersuites_list +#define _esp_tls_get_supported_groups_list esp_tls_custom_stack_get_supported_groups_list #else #error "No TLS stack configured" #endif @@ -693,6 +695,11 @@ const int *esp_tls_get_ciphersuites_list(void) return _esp_tls_get_ciphersuites_list(); } +const uint16_t *esp_tls_get_supported_groups_list(void) +{ + return _esp_tls_get_supported_groups_list(); +} + esp_err_t esp_tls_server_session_init(esp_tls_cfg_server_t *cfg, int sockfd, esp_tls_t *tls) { return _esp_tls_server_session_init(cfg, sockfd, tls); diff --git a/components/esp-tls/esp_tls.h b/components/esp-tls/esp_tls.h index 5f84f10813b..f5693c07642 100644 --- a/components/esp-tls/esp_tls.h +++ b/components/esp-tls/esp_tls.h @@ -7,6 +7,7 @@ #define _ESP_TLS_H_ #include +#include #include "esp_err.h" #include "esp_tls_errors.h" #include "esp_key_config.h" @@ -241,8 +242,9 @@ typedef struct esp_tls_cfg { #endif /* CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS */ esp_tls_addr_family_t addr_family; /*!< The address family to use when connecting to a host. */ - const int *ciphersuites_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites. - Please check the list validity by esp_tls_get_ciphersuites_list() API */ + const int *ciphersuites_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites. */ + const uint16_t *groups_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS supported groups + The array is not copied. It must stay valid for the lifetime of the connection. */ esp_tls_proto_ver_t tls_version; /*!< TLS protocol version of the connection, e.g., TLS 1.2, TLS 1.3 (default - no preference) */ #if CONFIG_MBEDTLS_DYNAMIC_BUFFER @@ -387,6 +389,12 @@ typedef struct esp_tls_cfg_server { const int *ciphersuites_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites. Please check the list validity by esp_tls_get_ciphersuites_list() API. This allows per-server cipher suite configuration. */ + + const uint16_t *groups_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS supported groups + (named groups for key exchange, formerly named curves). + The array is not copied. It must stay valid for the lifetime of the connection. + Please check the list validity by esp_tls_get_supported_groups_list() API. + This allows per-server group configuration. */ } esp_tls_cfg_server_t; /** @@ -739,13 +747,20 @@ mbedtls_x509_crt *esp_tls_get_global_ca_store(void); /** * @brief Get supported TLS ciphersuites list. * - * See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4 for the list of ciphersuites - * * @return Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites. * */ const int *esp_tls_get_ciphersuites_list(void); +/** + * @brief Get supported TLS groups list. + * + * @return Pointer to a zero-terminated array of IANA identifiers of TLS supported groups, + * or NULL if the configured TLS stack does not report the list. + * + */ +const uint16_t *esp_tls_get_supported_groups_list(void); + /** * @brief Initialize server side TLS/SSL connection * diff --git a/components/esp-tls/esp_tls_custom_stack.c b/components/esp-tls/esp_tls_custom_stack.c index ac4e92edfe4..048f03d9069 100644 --- a/components/esp-tls/esp_tls_custom_stack.c +++ b/components/esp-tls/esp_tls_custom_stack.c @@ -165,6 +165,16 @@ const int *esp_tls_custom_stack_get_ciphersuites_list(void) return s_esp_tls_custom_stack->get_ciphersuites_list(s_esp_tls_custom_stack_user_ctx); } +const uint16_t *esp_tls_custom_stack_get_supported_groups_list(void) +{ + /* This operation is optional. A stack that does not implement it reports no list. */ + if (s_esp_tls_custom_stack == NULL || !s_esp_tls_custom_stack->get_supported_groups_list) { + ESP_LOGD(TAG, "No TLS stack registered or supported groups list not available."); + return NULL; + } + return s_esp_tls_custom_stack->get_supported_groups_list(s_esp_tls_custom_stack_user_ctx); +} + void *esp_tls_custom_stack_get_client_session(esp_tls_t *tls) { if (s_esp_tls_custom_stack == NULL || !s_esp_tls_custom_stack->get_client_session) { diff --git a/components/esp-tls/esp_tls_custom_stack.h b/components/esp-tls/esp_tls_custom_stack.h index 83bcc9c23f9..b68185ca2b4 100644 --- a/components/esp-tls/esp_tls_custom_stack.h +++ b/components/esp-tls/esp_tls_custom_stack.h @@ -25,7 +25,7 @@ extern "C" { * when registering a custom TLS stack. It allows ESP-TLS to detect incompatible * interface changes between ESP-IDF versions and custom stack implementations. */ -#define ESP_TLS_STACK_OPS_VERSION 1 +#define ESP_TLS_STACK_OPS_VERSION 2 /** * @brief TLS stack operations vtable @@ -320,6 +320,24 @@ typedef struct esp_tls_stack_ops { */ const int *(*get_ciphersuites_list)(void *user_ctx); + /** + * @brief Get list of supported groups (optional) + * + * This function returns a list of IANA supported group identifiers, the named groups + * that the stack can use for key exchange. This is used by the + * esp_tls_get_supported_groups_list() API. + * + * @param user_ctx User context pointer passed to esp_tls_register_stack() + * + * @return Pointer to zero-terminated array of IANA supported group identifiers. + * The array must be terminated with 0. + * + * @note The returned pointer should point to static/const data (not dynamically allocated). + * @note Group identifiers are IANA standard values (e.g., 0x0017 for secp256r1). + * @note This field can be NULL. esp_tls_get_supported_groups_list() then returns NULL. + */ + const uint16_t *(*get_supported_groups_list)(void *user_ctx); + /** * @brief Get client session (optional, for session ticket support) * @@ -532,9 +550,9 @@ typedef struct esp_tls_stack_ops { * Compile-time check to detect structure layout changes. * If this assertion fails, you MUST increment ESP_TLS_STACK_OPS_VERSION. * - * Field count: 1 (version) + 23 (function pointers) = 24 + * Field count: 1 (version) + 24 (function pointers) = 25 */ -ESP_STATIC_ASSERT(sizeof(esp_tls_stack_ops_t) == 24 * sizeof(void *), "esp_tls_stack_ops_t layout changed - update ESP_TLS_STACK_OPS_VERSION!"); +ESP_STATIC_ASSERT(sizeof(esp_tls_stack_ops_t) == 25 * sizeof(void *), "esp_tls_stack_ops_t layout changed - update ESP_TLS_STACK_OPS_VERSION!"); /** * @brief Register a custom TLS stack implementation @@ -578,6 +596,7 @@ ESP_STATIC_ASSERT(sizeof(esp_tls_stack_ops_t) == 24 * sizeof(void *), "esp_tls_s * - server_session_init (server-side, can be NULL if server_session_create is provided) * - server_session_continue_async (server-side, can be NULL if server_session_create is provided) * - server_session_delete (server-side, can be NULL, conn_delete will be used) + * - get_supported_groups_list (list of named groups for key exchange) * @param user_ctx User context pointer that will be passed to all callbacks as the first parameter. * This allows C++ implementations to avoid singletons by passing instance pointers. * Can be NULL if not needed. @@ -696,6 +715,9 @@ void esp_tls_custom_stack_free_global_ca_store(void); /** @brief Get list of supported ciphersuites */ const int *esp_tls_custom_stack_get_ciphersuites_list(void); +/** @brief Get list of supported groups. Returns NULL if the stack does not provide the list. */ +const uint16_t *esp_tls_custom_stack_get_supported_groups_list(void); + /** @brief Get client session ticket for reuse */ void *esp_tls_custom_stack_get_client_session(esp_tls_t *tls); diff --git a/components/esp-tls/esp_tls_mbedtls.c b/components/esp-tls/esp_tls_mbedtls.c index aeabfeab879..1fae3ddec82 100644 --- a/components/esp-tls/esp_tls_mbedtls.c +++ b/components/esp-tls/esp_tls_mbedtls.c @@ -758,6 +758,23 @@ void esp_mbedtls_server_session_ticket_ctx_free(esp_tls_server_session_ticket_ct } #endif +/** + * @brief Apply a user-provided list of TLS supported groups to an mbedtls configuration. + * + * The list is owned by the caller. mbedtls keeps the pointer, it does not copy the array, + * so the array must stay valid for the lifetime of the connection. + * An empty list is treated as "not configured", because mbedtls reads a zero-terminated array. + */ +static void set_groups_list(mbedtls_ssl_config *conf, const uint16_t *groups_list) +{ + if (groups_list == NULL || groups_list[0] == MBEDTLS_SSL_IANA_TLS_GROUP_NONE) { + ESP_LOGD(TAG, "No custom supported groups provided - using default"); + return; + } + ESP_LOGD(TAG, "Set the supported groups list (user-provided)"); + mbedtls_ssl_conf_groups(conf, groups_list); +} + static esp_err_t set_server_config(esp_tls_cfg_server_t *cfg, esp_tls_t *tls) { assert(cfg != NULL); @@ -965,6 +982,8 @@ static esp_err_t set_server_config(esp_tls_cfg_server_t *cfg, esp_tls_t *tls) } else { ESP_LOGD(TAG, "No custom cipher suites provided - using default"); } + set_groups_list(&tls->conf, cfg->groups_list); + return ESP_OK; } @@ -1244,6 +1263,8 @@ esp_err_t set_client_config(const char *hostname, size_t hostlen, esp_tls_cfg_t ESP_LOGD(TAG, "Set the ciphersuites list"); mbedtls_ssl_conf_ciphersuites(&tls->conf, cfg->ciphersuites_list); } + set_groups_list(&tls->conf, cfg->groups_list); + return ESP_OK; } @@ -1399,6 +1420,11 @@ const int *esp_mbedtls_get_ciphersuites_list(void) return mbedtls_ssl_list_ciphersuites(); } +const uint16_t *esp_mbedtls_get_supported_groups_list(void) +{ + return mbedtls_ssl_get_supported_group_list(); +} + #ifdef CONFIG_ESP_TLS_USE_DS_PERIPHERAL /* * tf-psa-crypto 1.1 made mbedtls_pk_wrap_psa() call psa_export_public_key() on diff --git a/components/esp-tls/private_include/esp_tls_mbedtls.h b/components/esp-tls/private_include/esp_tls_mbedtls.h index 05394a73d71..6b3ec1eb928 100644 --- a/components/esp-tls/private_include/esp_tls_mbedtls.h +++ b/components/esp-tls/private_include/esp_tls_mbedtls.h @@ -145,3 +145,8 @@ void esp_mbedtls_free_global_ca_store(void); * Internal Callback for esp_tls_get_ciphersuites_list */ const int *esp_mbedtls_get_ciphersuites_list(void); + +/** + * Internal Callback for esp_tls_get_supported_groups_list + */ +const uint16_t *esp_mbedtls_get_supported_groups_list(void); diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c index 406b51e3978..15a64a8fc7e 100644 --- a/components/esp_http_client/esp_http_client.c +++ b/components/esp_http_client/esp_http_client.c @@ -1016,6 +1016,14 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co if (config->common_name) { esp_transport_ssl_set_common_name(ssl, config->common_name); } + + if (config->ciphersuites_list) { + esp_transport_ssl_set_ciphersuites_list(ssl, config->ciphersuites_list); + } + + if (config->groups_list) { + esp_transport_ssl_set_groups_list(ssl, config->groups_list); + } #endif if (_set_config(client, config) != ESP_OK) { diff --git a/components/esp_http_client/include/esp_http_client.h b/components/esp_http_client/include/esp_http_client.h index 290786bc1cb..10931796abc 100644 --- a/components/esp_http_client/include/esp_http_client.h +++ b/components/esp_http_client/include/esp_http_client.h @@ -9,6 +9,7 @@ #include "freertos/FreeRTOS.h" #include "sdkconfig.h" +#include #include "esp_err.h" #include "esp_key_config.h" #include @@ -238,6 +239,14 @@ typedef struct { #if CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS const char **alpn_protos; /*!< Application protocols required for HTTP2. If HTTP2/ALPN support is required, a list of protocols that should be negotiated. The format is length followed by protocol name. For the most common cases the following is ok: const char **alpn_protos = { "h2", NULL }; - where 'h2' is the protocol name */ + const int *ciphersuites_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites. + The array is not copied. It must stay valid until the client is cleaned up. + Please check the list validity by esp_tls_get_ciphersuites_list() API. + NULL keeps the default ciphersuites. */ + const uint16_t *groups_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS supported groups, + the named groups that are used for key exchange. The array is not copied. It must + stay valid until the client is cleaned up. Please check the list validity by + esp_tls_get_supported_groups_list() API. NULL keeps the default groups. */ #endif #if CONFIG_ESP_TLS_USE_DS_PERIPHERAL void *ds_data; /*!< Pointer for digital signature peripheral context, see ESP-TLS Documentation for more details */ diff --git a/components/esp_https_server/include/esp_https_server.h b/components/esp_https_server/include/esp_https_server.h index 499891261d1..c63381edbf5 100644 --- a/components/esp_https_server/include/esp_https_server.h +++ b/components/esp_https_server/include/esp_https_server.h @@ -164,6 +164,13 @@ struct httpd_ssl_config { * Please check the list validity by esp_tls_get_ciphersuites_list() API. * This allows per-server cipher suite configuration. */ const int *ciphersuites_list; + + /** Pointer to a zero-terminated array of IANA identifiers of TLS supported groups, + * the named groups that are used for key exchange. + * Please check the list validity by esp_tls_get_supported_groups_list() API. + * The array is not copied. It must stay valid for the lifetime of the server. + * This allows per-server group configuration. */ + const uint16_t *groups_list; }; typedef struct httpd_ssl_config httpd_ssl_config_t; @@ -242,6 +249,7 @@ typedef struct httpd_ssl_config httpd_ssl_config_t; .tls_handshake_timeout_ms = 0, \ .tls_version = ESP_TLS_VER_ANY, \ .ciphersuites_list = NULL, \ + .groups_list = NULL, \ } /** diff --git a/components/esp_https_server/src/https_server.c b/components/esp_https_server/src/https_server.c index 16af012c2cb..13e7bb7ebe2 100644 --- a/components/esp_https_server/src/https_server.c +++ b/components/esp_https_server/src/https_server.c @@ -304,6 +304,7 @@ static esp_err_t create_secure_context(const struct httpd_ssl_config *config, ht cfg->tls_version = config->tls_version; cfg->ciphersuites_list = config->ciphersuites_list; + cfg->groups_list = config->groups_list; #if defined(CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK) cfg->cert_select_cb = config->cert_select_cb; diff --git a/components/tcp_transport/include/esp_transport_ssl.h b/components/tcp_transport/include/esp_transport_ssl.h index 620b1c306c9..fc4d26cd5de 100644 --- a/components/tcp_transport/include/esp_transport_ssl.h +++ b/components/tcp_transport/include/esp_transport_ssl.h @@ -206,6 +206,20 @@ void esp_transport_ssl_set_common_name(esp_transport_handle_t t, const char *com */ void esp_transport_ssl_set_ciphersuites_list(esp_transport_handle_t t, const int *ciphersuites_list); +/** + * @brief Set the list of supported groups for the TLS connection + * + * @note This function stores a pointer to the data rather than making a copy. + * Therefore, the data must remain valid until the connection is cleaned up. + * The `groups_list` is a pointer to a zero-terminated array of IANA identifiers + * of TLS supported groups, the named groups that are used for key exchange. + * You can verify the validity of the list using the `esp_tls_get_supported_groups_list()` API. + * + * @param t SSL transport + * @param[in] groups_list A pointer to a zero-terminated array of IANA identifiers of TLS supported groups + */ +void esp_transport_ssl_set_groups_list(esp_transport_handle_t t, const uint16_t *groups_list); + /** * @brief Set the ssl context to use secure element (atecc608a) for client(device) private key and certificate * diff --git a/components/tcp_transport/transport_ssl.c b/components/tcp_transport/transport_ssl.c index f3059d275c6..8a92d1a465a 100644 --- a/components/tcp_transport/transport_ssl.c +++ b/components/tcp_transport/transport_ssl.c @@ -502,6 +502,12 @@ void esp_transport_ssl_set_ciphersuites_list(esp_transport_handle_t t, const int ssl->cfg.ciphersuites_list = ciphersuites_list; } +void esp_transport_ssl_set_groups_list(esp_transport_handle_t t, const uint16_t *groups_list) +{ + GET_SSL_FROM_TRANSPORT_OR_RETURN(ssl, t); + ssl->cfg.groups_list = groups_list; +} + /* Deprecated and non-functional; kept only for source compatibility. Setting * use_secure_element makes the connection fail with ESP_ERR_NOT_SUPPORTED. */ void esp_transport_ssl_use_secure_element(esp_transport_handle_t t) diff --git a/docs/en/api-reference/protocols/esp_http_client.rst b/docs/en/api-reference/protocols/esp_http_client.rst index ce16b294028..b7020cae4d5 100644 --- a/docs/en/api-reference/protocols/esp_http_client.rst +++ b/docs/en/api-reference/protocols/esp_http_client.rst @@ -242,6 +242,25 @@ The TLS protocol version for the HTTP client can be configured as follows: .tls_version = ESP_HTTP_CLIENT_TLS_VER_TLS_1_2, }; +TLS Ciphersuites and Supported Groups +------------------------------------- + +The ciphersuites and the supported groups of the underlying TLS connection can be set in :cpp:type:`esp_http_client_config_t`. Both lists apply to this client only. Please refer to the **TLS Ciphersuites** section and to the **TLS Supported Groups** section in the :doc:`/api-reference/protocols/esp_tls` for more details. + +The client keeps a pointer to each array. It does not copy the arrays. Each array must end with 0 and must stay valid until :cpp:func:`esp_http_client_cleanup` is called. + + .. code-block:: c + + #include "esp_http_client.h" + static const int ciphersuites_list[] = {MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0}; + static const uint16_t groups_list[] = {MBEDTLS_SSL_IANA_TLS_GROUP_X25519, MBEDTLS_SSL_IANA_TLS_GROUP_SECP256R1, 0}; + esp_http_client_config_t config = { + .ciphersuites_list = ciphersuites_list, + .groups_list = groups_list, + }; + +Call :cpp:func:`esp_tls_get_ciphersuites_list` and :cpp:func:`esp_tls_get_supported_groups_list` to get the lists that the TLS stack supports, and cross-check them against the supplied lists. + API Reference ------------- diff --git a/docs/en/api-reference/protocols/esp_https_server.rst b/docs/en/api-reference/protocols/esp_https_server.rst index c0482201fd7..e068eb10ec7 100644 --- a/docs/en/api-reference/protocols/esp_https_server.rst +++ b/docs/en/api-reference/protocols/esp_https_server.rst @@ -94,6 +94,22 @@ When enabled, you can set the certificate selection callback using the :cpp:memb }; +TLS Ciphersuites and Supported Groups +------------------------------------- + +The ciphersuites and the supported groups of the server can be set in :cpp:type:`httpd_ssl_config_t`. Both lists apply to this server instance only. Please refer to the **TLS Ciphersuites** section and to the **TLS Supported Groups** section in the :doc:`/api-reference/protocols/esp_tls` for more details. + +The server keeps a pointer to each array. It does not copy the arrays. Each array must end with 0 and must stay valid for the lifetime of the server. + +.. code-block:: c + + static const int ciphersuites_list[] = {MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0}; + static const uint16_t groups_list[] = {MBEDTLS_SSL_IANA_TLS_GROUP_X25519, MBEDTLS_SSL_IANA_TLS_GROUP_SECP256R1, 0}; + + httpd_ssl_config_t cfg = HTTPD_SSL_CONFIG_DEFAULT(); + cfg.ciphersuites_list = ciphersuites_list; + cfg.groups_list = groups_list; + API Reference ------------- diff --git a/docs/en/api-reference/protocols/esp_tls.rst b/docs/en/api-reference/protocols/esp_tls.rst index e1da771f436..bce9fcaead3 100644 --- a/docs/en/api-reference/protocols/esp_tls.rst +++ b/docs/en/api-reference/protocols/esp_tls.rst @@ -143,6 +143,7 @@ To use a custom TLS stack in your project, follow these steps: * ``server_session_init`` - Initialize server session (server-side, can be NULL if server_session_create is provided) * ``server_session_continue_async`` - Continue async server handshake (server-side, can be NULL if server_session_create is provided) * ``server_session_delete`` - Delete server session (server-side, can be NULL, conn_delete will be used) + * ``get_supported_groups_list`` - Get list of supported groups (can be NULL, the list is then reported as unavailable) 3. Create a static/global structure containing your function implementations: @@ -423,6 +424,31 @@ You can set ``ciphersuites_list`` in the :cpp:type:`esp_tls_cfg_t` structure dur ESP-TLS will not check the validity of ``ciphersuites_list`` that was set, you should call :cpp:func:`esp_tls_get_ciphersuites_list` to get ciphersuites list supported in the TLS stack and cross-check it against the supplied list. +.. note:: + + This feature is supported only in the MbedTLS stack. + +TLS Supported Groups +-------------------- + +ESP-TLS provides the ability to set a supported groups list. The groups are the named groups that are used for key exchange, formerly named curves. The list informs the peer about the groups that are offered for the specific TLS connection, regardless of the TLS stack configuration. The list applies to the client mode and to the server mode. + +You can set ``groups_list`` in the :cpp:type:`esp_tls_cfg_t` structure, or in the :cpp:type:`esp_tls_cfg_server_t` structure, as follows: + +.. code-block:: c + + /* groups_list must end with 0 and must be available in the memory scope active during the entire TLS connection */ + static const uint16_t groups_list[] = {MBEDTLS_SSL_IANA_TLS_GROUP_X25519, MBEDTLS_SSL_IANA_TLS_GROUP_SECP256R1, 0}; + esp_tls_cfg_t cfg = { + .groups_list = groups_list, + }; + +ESP-TLS will not check the validity of ``groups_list`` that was set, you should call :cpp:func:`esp_tls_get_supported_groups_list` to get the groups list supported in the TLS stack and cross-check it against the supplied list. + +.. note:: + + A group list that excludes the group of the peer costs one HelloRetryRequest in TLS 1.3. In TLS 1.2 the handshake fails. + .. note:: This feature is supported only in the MbedTLS stack. diff --git a/examples/protocols/esp_http_client/README.md b/examples/protocols/esp_http_client/README.md index 4e130b6607f..1a4cfcb818f 100644 --- a/examples/protocols/esp_http_client/README.md +++ b/examples/protocols/esp_http_client/README.md @@ -3,3 +3,36 @@ # ESP HTTP Client Example See the README.md file in the upper level 'examples' directory for more information about examples. + +## TLS ciphersuite and group configuration + +`esp_http_client_config_t` has two fields that restrict the TLS handshake of +one client: + +* `ciphersuites_list` is a zero-terminated array of IANA ciphersuite + identifiers. +* `groups_list` is a zero-terminated array of IANA supported group + identifiers. The groups are the named groups that are used for key exchange. + +`https_with_restricted_tls_lists()` in `main/esp_http_client_example.c` shows +both fields. At startup the example also logs the full lists that the TLS +stack supports, through `esp_tls_get_ciphersuites_list()` and +`esp_tls_get_supported_groups_list()`. + +Note the rules of the two lists: + +* The client does not copy the arrays. Each array must stay valid until + `esp_http_client_cleanup()`. The example uses static arrays. +* A listed ciphersuite works only if its modules are enabled in `menuconfig`. + mbedtls does no dependency check on the list. +* A listed group works only if its curve is enabled in `menuconfig`. +* The ciphersuites must match the key type of the server certificate. The + example list holds an ECDSA suite and an RSA suite for this reason. +* One ciphersuite list covers TLS 1.2 and TLS 1.3. A list that holds only + TLS 1.2 suites makes a TLS 1.3 handshake fail. The example list adds a + TLS 1.3 suite when `CONFIG_MBEDTLS_SSL_PROTO_TLS1_3` is enabled. +* A group list that excludes the group of the server costs one + HelloRetryRequest in TLS 1.3. In TLS 1.2 the handshake fails. + +For a server, use the `ciphersuites_list` and `groups_list` fields of +`httpd_ssl_config_t`. diff --git a/examples/protocols/esp_http_client/main/CMakeLists.txt b/examples/protocols/esp_http_client/main/CMakeLists.txt index d4f9779c2a9..8309c9e1267 100644 --- a/examples/protocols/esp_http_client/main/CMakeLists.txt +++ b/examples/protocols/esp_http_client/main/CMakeLists.txt @@ -1,7 +1,8 @@ # Embed the server root certificate into the final binary # # (If this was a component, we would set COMPONENT_EMBED_TXTFILES here.) -set(requires esp-tls nvs_flash esp_event esp_netif esp_http_client) +# mbedtls is named for mbedtls/ssl.h, used for the IANA ciphersuite and group macros. +set(requires esp-tls mbedtls nvs_flash esp_event esp_netif esp_http_client) idf_build_get_property(target IDF_TARGET) if(${target} STREQUAL "linux") diff --git a/examples/protocols/esp_http_client/main/esp_http_client_example.c b/examples/protocols/esp_http_client/main/esp_http_client_example.c index 658317b7ae9..d76f1e56e59 100644 --- a/examples/protocols/esp_http_client/main/esp_http_client_example.c +++ b/examples/protocols/esp_http_client/main/esp_http_client_example.c @@ -18,6 +18,8 @@ #include "protocol_examples_common.h" #include "protocol_examples_utils.h" #include "esp_tls.h" +#include "mbedtls/ssl.h" +#include "mbedtls/ssl_ciphersuites.h" #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE #include "esp_crt_bundle.h" #endif @@ -1049,8 +1051,84 @@ static void http_partial_download(void) } #endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE +/* Log the ciphersuites and the groups that the TLS stack supports. The lists + * come from the modules and the curves that are enabled in menuconfig. A list + * that is set on a connection must be a subset of these lists. + */ +static void log_supported_tls_lists(void) +{ + const int *ciphersuites = esp_tls_get_ciphersuites_list(); + ESP_LOGI(TAG, "Supported ciphersuites:"); + for (size_t i = 0; ciphersuites != NULL && ciphersuites[i] != 0; i++) { + ESP_LOGI(TAG, " 0x%04x %s", (unsigned)ciphersuites[i], + mbedtls_ssl_get_ciphersuite_name(ciphersuites[i])); + } + + const uint16_t *groups = esp_tls_get_supported_groups_list(); + ESP_LOGI(TAG, "Supported groups:"); + for (size_t i = 0; groups != NULL && groups[i] != MBEDTLS_SSL_IANA_TLS_GROUP_NONE; i++) { + ESP_LOGI(TAG, " 0x%04x", (unsigned)groups[i]); + } +} + +#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE +/* Ciphersuites that this example offers. The list holds one suite for an RSA + * server certificate and one for an ECDSA server certificate. It adds a TLS 1.3 + * suite when TLS 1.3 is enabled, because a TLS 1.2 only list makes a TLS 1.3 + * handshake fail. Every listed suite must have its modules enabled in + * menuconfig, or the handshake ignores the suite. + */ +static const int s_ciphersuites_list[] = { +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) + MBEDTLS_TLS1_3_AES_128_GCM_SHA256, +#endif + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + 0, +}; + +/* Named groups that this example offers for key exchange. A list that excludes + * the group of the server costs one HelloRetryRequest in TLS 1.3, and fails the + * handshake in TLS 1.2. + */ +static const uint16_t s_groups_list[] = { + MBEDTLS_SSL_IANA_TLS_GROUP_X25519, + MBEDTLS_SSL_IANA_TLS_GROUP_SECP256R1, + MBEDTLS_SSL_IANA_TLS_GROUP_NONE, +}; + +/* Restrict the ciphersuites and the groups of one connection. The restriction + * applies to this client only. Both arrays are static, because esp_http_client + * keeps the pointers and does not copy the arrays. + */ +static void https_with_restricted_tls_lists(void) +{ + esp_http_client_config_t config = { + .url = "https://www.howsmyssl.com/a/check", + .event_handler = _http_event_handler, + .crt_bundle_attach = esp_crt_bundle_attach, + .timeout_ms = 5000, + .ciphersuites_list = s_ciphersuites_list, + .groups_list = s_groups_list, + }; + ESP_LOGI(TAG, "HTTPS request with a restricted ciphersuite and group list =>"); + esp_http_client_handle_t client = esp_http_client_init(&config); + esp_err_t err = esp_http_client_perform(client); + + if (err == ESP_OK) { + ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRId64, + esp_http_client_get_status_code(client), + esp_http_client_get_content_length(client)); + } else { + ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err)); + } + esp_http_client_cleanup(client); +} +#endif // CONFIG_MBEDTLS_CERTIFICATE_BUNDLE + static void http_test_task(void *pvParameters) { + log_supported_tls_lists(); http_rest_with_url(); http_rest_with_hostname_path(); #if CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH @@ -1081,6 +1159,7 @@ static void http_test_task(void *pvParameters) http_chunked_request_async(); #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE http_partial_download(); + https_with_restricted_tls_lists(); #endif ESP_LOGI(TAG, "Finish http example");