diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index 994560d532..26e82f50b6 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -160,6 +160,11 @@ int esp_tls_conn_destroy(esp_tls_t *tls) ret = close(tls->sockfd); } esp_tls_internal_event_tracker_destroy(tls->error_handle); +#if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 && CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS + if (tls->client_session) { + free(tls->client_session); + } +#endif // CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 && CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS free(tls); tls = NULL; return ret; @@ -180,6 +185,10 @@ esp_tls_t *esp_tls_init(void) } _esp_tls_net_init(tls); tls->sockfd = -1; +#if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 && CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS + tls->client_session = NULL; + tls->client_session_len = 0; +#endif // CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 && CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS return tls; } diff --git a/components/esp-tls/esp_tls_mbedtls.c b/components/esp-tls/esp_tls_mbedtls.c index b5af9312f4..6efa07534c 100644 --- a/components/esp-tls/esp_tls_mbedtls.c +++ b/components/esp-tls/esp_tls_mbedtls.c @@ -190,12 +190,38 @@ esp_tls_client_session_t *esp_mbedtls_get_client_session(esp_tls_t *tls) return NULL; } - esp_tls_client_session_t *client_session = (esp_tls_client_session_t*)calloc(1, sizeof(esp_tls_client_session_t)); + esp_tls_client_session_t *client_session = NULL; + +#if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 + /* For TLS 1.3, check if the session ticket is saved in the esp-tls context */ + if (mbedtls_ssl_get_version_number(&tls->ssl) == MBEDTLS_SSL_VERSION_TLS1_3) { + if (tls->client_session == NULL) { + return NULL; + } else { + client_session = calloc(1, sizeof(esp_tls_client_session_t)); + if (client_session == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for client session ctx"); + return NULL; + } + /* If the session ticket is saved in the esp-tls context, load it into the client session */ + int ret = mbedtls_ssl_session_load(&client_session->saved_session, tls->client_session, tls->client_session_len); + if (ret != 0) { + ESP_LOGE(TAG, "Error in loading the client ssl session"); + free(client_session); + return NULL; + } + return client_session; + } + } +#endif + /* In case of TLS 1.2, the session context is available as long as the connection is active */ + client_session = (esp_tls_client_session_t*)calloc(1, sizeof(esp_tls_client_session_t)); if (client_session == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for client session ctx"); return NULL; } + /* Get the session ticket from the mbedtls context and load it into the client session */ int ret = mbedtls_ssl_get_session(&tls->ssl, &(client_session->saved_session)); if (ret != 0) { ESP_LOGE(TAG, "Error in obtaining the client ssl session"); @@ -259,18 +285,81 @@ ssize_t esp_mbedtls_read(esp_tls_t *tls, char *data, size_t datalen) { ssize_t ret = mbedtls_ssl_read(&tls->ssl, (unsigned char *)data, datalen); -#if CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS - // If a post-handshake message is received, connection state is changed to `MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET` - // Call mbedtls_ssl_read() till state is `MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET` or return code is `MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET` - // to process session tickets in TLS 1.3 connection +#if defined(CONFIG_MBEDTLS_SSL_PROTO_TLS1_3) + /* + * As per RFC 8446, section 4.6.1 the server may send a NewSessionTicket message at any time after the + * client Finished message. + * If a post-handshake message is received, connection state is changed to `MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET` + * Call mbedtls_ssl_read() till state is `MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET` or return code is `MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET` + * to process session tickets in TLS 1.3 connection. + * This handshake message should be processed by mbedTLS and not by the application. + */ if (mbedtls_ssl_get_version_number(&tls->ssl) == MBEDTLS_SSL_VERSION_TLS1_3) { while (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET || tls->ssl.MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET) { ESP_LOGD(TAG, "got session ticket in TLS 1.3 connection, retry read"); +#if CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS + if (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) { + esp_tls_client_session_t *tls13_saved_client_session = calloc(1, sizeof(esp_tls_client_session_t)); + if (tls13_saved_client_session == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for client session ctx"); + return ESP_ERR_NO_MEM; + } + + ret = mbedtls_ssl_get_session(&tls->ssl, &tls13_saved_client_session->saved_session); + if (ret != 0) { + ESP_LOGE(TAG, "Error in getting the client ssl session"); + free(tls13_saved_client_session); + tls13_saved_client_session = NULL; + return ESP_ERR_MBEDTLS_SSL_HANDSHAKE_FAILED; + } + ESP_LOGD(TAG, "Session ticket received"); + + size_t session_ticket_len = 0; + ret = mbedtls_ssl_session_save(&tls13_saved_client_session->saved_session, NULL, 0, &session_ticket_len); + if (ret != MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL) { + ESP_LOGE(TAG, "Error in getting the client ssl session length"); + free(tls13_saved_client_session); + tls13_saved_client_session = NULL; + return ESP_ERR_MBEDTLS_SSL_HANDSHAKE_FAILED; + } + + ESP_LOGD(TAG, "Session ticket length: %zu", session_ticket_len); + if (tls->client_session != NULL) { + free(tls->client_session); + tls->client_session = NULL; + } + /* Allocate memory for the session ticket */ + tls->client_session = calloc(1, session_ticket_len); + if (tls->client_session == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for client session ctx"); + free(tls13_saved_client_session); + tls13_saved_client_session = NULL; + return ESP_ERR_NO_MEM; + } + ret = mbedtls_ssl_session_save(&tls13_saved_client_session->saved_session, (unsigned char *)tls->client_session, session_ticket_len, &session_ticket_len); + if (ret != 0) { + ESP_LOGE(TAG, "Error in saving the client ssl session"); + mbedtls_print_error_msg(ret); + free(tls->client_session); + tls->client_session = NULL; + free(tls13_saved_client_session); + tls13_saved_client_session = NULL; + return ESP_ERR_MBEDTLS_SSL_HANDSHAKE_FAILED; + } + + ESP_LOGD(TAG, "Session ticket saved in the client session context"); + tls->client_session_len = session_ticket_len; + mbedtls_ssl_session_free(&tls13_saved_client_session->saved_session); + free(tls13_saved_client_session); + tls13_saved_client_session = NULL; + } +#endif // CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS + /* After handling the session ticket, we need to attempt to read again + * to either get application data or process another ticket */ ret = mbedtls_ssl_read(&tls->ssl, (unsigned char *)data, datalen); } } -#endif // CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS - +#endif // CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 if (ret < 0) { if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { return 0; @@ -762,9 +851,14 @@ esp_err_t set_client_config(const char *hostname, size_t hostlen, esp_tls_cfg_t ESP_LOGD(TAG, "Enabling client-side tls session ticket support"); mbedtls_ssl_conf_session_tickets(&tls->conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED); mbedtls_ssl_conf_renegotiation(&tls->conf, MBEDTLS_SSL_RENEGOTIATION_ENABLED); - #endif /* CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS */ +#if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 +#if CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS || CONFIG_MBEDTLS_DYNAMIC_BUFFER + mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(&tls->conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED); +#endif +#endif + if (cfg->crt_bundle_attach != NULL) { #ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE ESP_LOGD(TAG, "Use certificate bundle"); diff --git a/components/esp-tls/private_include/esp_tls_private.h b/components/esp-tls/private_include/esp_tls_private.h index 669e01aad6..123f02b621 100644 --- a/components/esp-tls/private_include/esp_tls_private.h +++ b/components/esp-tls/private_include/esp_tls_private.h @@ -68,6 +68,10 @@ struct esp_tls { bool use_ecdsa_peripheral; /*!< Use the ECDSA peripheral for the private key operations. */ uint8_t ecdsa_efuse_blk; /*!< The efuse block number where the ECDSA key is stored. */ #endif +#if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 && CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS + unsigned char *client_session; /*!< Pointer for the serialized client session ticket context. */ + size_t client_session_len; /*!< Length of the serialized client session ticket context. */ +#endif /* CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 && CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS */ #elif CONFIG_ESP_TLS_USING_WOLFSSL void *priv_ctx; void *priv_ssl; diff --git a/components/mbedtls/CMakeLists.txt b/components/mbedtls/CMakeLists.txt index bfd405f7c1..4308a0a7c9 100644 --- a/components/mbedtls/CMakeLists.txt +++ b/components/mbedtls/CMakeLists.txt @@ -351,6 +351,7 @@ if(CONFIG_MBEDTLS_DYNAMIC_BUFFER) set(WRAP_FUNCTIONS mbedtls_ssl_write_client_hello mbedtls_ssl_handshake_client_step + mbedtls_ssl_tls13_handshake_client_step mbedtls_ssl_handshake_server_step mbedtls_ssl_read mbedtls_ssl_write diff --git a/components/mbedtls/Kconfig b/components/mbedtls/Kconfig index d499956a2c..4a38500611 100644 --- a/components/mbedtls/Kconfig +++ b/components/mbedtls/Kconfig @@ -172,10 +172,10 @@ menu "mbedTLS" default 4 if MBEDTLS_DEBUG_LEVEL_VERBOSE menu "mbedTLS v3.x related" - # NOTE: MBEDTLS_DYNAMIC_BUFFER feature is not supported with TLS 1.3 yet. Ref: IDF-4762 config MBEDTLS_SSL_PROTO_TLS1_3 bool "Support TLS 1.3 protocol" - depends on MBEDTLS_TLS_ENABLED && MBEDTLS_SSL_KEEP_PEER_CERTIFICATE && !MBEDTLS_DYNAMIC_BUFFER + depends on MBEDTLS_TLS_ENABLED && MBEDTLS_SSL_KEEP_PEER_CERTIFICATE + select MBEDTLS_CLIENT_SSL_SESSION_TICKETS if MBEDTLS_DYNAMIC_BUFFER select MBEDTLS_HKDF_C default n diff --git a/components/mbedtls/port/dynamic/dynamic_buffer_architecture.md b/components/mbedtls/port/dynamic/dynamic_buffer_architecture.md new file mode 100644 index 0000000000..2e19eba941 --- /dev/null +++ b/components/mbedtls/port/dynamic/dynamic_buffer_architecture.md @@ -0,0 +1,221 @@ +# Dynamic Buffer Management in mbedTLS for ESP-IDF + +## Executive Summary + +ESP-IDF implements a dynamic buffer management system for mbedTLS to optimize memory usage during TLS/SSL connections. This architecture significantly reduces RAM requirements on memory-constrained ESP devices by intelligently allocating buffers only when needed and sizing them according to actual message requirements rather than worst-case scenarios. + +## The Problem We're Solving + +Standard TLS implementations allocate large static buffers that: +- Reserve memory for the entire connection lifetime +- Are sized for worst-case scenarios (large certificates, messages) +- Remain allocated even when not in use + +On memory-constrained IoT devices like ESP32, this traditional approach is inefficient and limits the number of concurrent connections possible. + +## Our Solution: The Dynamic Buffer Approach + +Instead of static allocation, our system: +1. **Allocates buffers only when needed** +2. **Right-sizes buffers** based on actual message requirements +3. **Releases memory** when it's not needed +4. **Preserves critical state** in small cache buffers + +## How It Works: A Conceptual View + +### Buffer Lifecycle + +1. **Starting state**: Begin with minimal or no buffer allocation +2. **Just before data transmission/reception**: Allocate right-sized buffer +3. **During data processing**: Use the allocated buffer +4. **After processing**: Replace large buffer with small cache buffer +5. **Repeat** as needed during the connection + +### Key Concepts Illustrated + +#### Transmission (TX) Buffer Handling + +``` +[Small idle buffer] → [Right-sized TX buffer] → [Back to small buffer] +``` + +#### Reception (RX) Buffer Handling + +``` +[No buffer] → [Header buffer] → [Right-sized RX buffer] → [Small cache buffer] +``` + +### Implementation Strategy + +Our implementation follows these steps for each operation: + +1. **Before operation**: Check if we need to allocate or resize a buffer +2. **During operation**: Use the standard mbedTLS functions with our buffers +3. **After operation**: Shrink or release buffers that are no longer needed + +## Core Components + +### 1. Custom Buffer Structure + +We use a custom buffer structure that includes metadata: + +```c +struct esp_mbedtls_ssl_buf { + esp_mbedtls_ssl_buf_states state; // CACHED or NOT_CACHED + unsigned int len; // Buffer size + unsigned char buf[0]; // Flexible array for actual data +}; +``` + +This structure allows us to: + +- Track whether a buffer contains important state +- Store the buffer's size for dynamic resizing +- Use a flexible array member for efficient memory layout + +### 2. Buffer States + +- **CACHED**: Contains important cryptographic state that must be preserved +- **NOT_CACHED**: Can be safely replaced or released + +The state tracking is critical for maintaining TLS protocol security while optimizing memory. + +### 3. Critical State Preservation + +When replacing large buffers with small cache buffers, we preserve: + +- **SSL counter values**: Used for replay protection +- **Initialization vectors**: Required for encryption/decryption + +These small amounts of cryptographic state must be maintained between operations to keep the TLS connection secure. + +### 4. Memory Management Functions + +| Function | Purpose | +|----------|---------| +| `esp_mbedtls_add_tx_buffer()` | Allocates a transmission buffer sized for the outgoing message | +| `esp_mbedtls_free_tx_buffer()` | Replaces TX buffer with small cache buffer after transmission | +| `esp_mbedtls_add_rx_buffer()` | Reads record header and allocates right-sized reception buffer | +| `esp_mbedtls_free_rx_buffer()` | Replaces RX buffer with small cache buffer after processing | + +### 5. Function Wrapping + +We intercept key mbedTLS functions using GCC's function wrapping: + +```c +int __wrap_mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len) { + // 1. Allocate right-sized buffer + // 2. Call original function + // 3. Free buffer when done +} +``` + +This allows seamless integration without modifying the mbedTLS source code. + +## The Handshake Process Design + +During TLS handshaking, memory needs change dramatically between steps. Our system tracks the handshake state and manages memory accordingly: + +### Client-Side Handshake Memory Management + +| Handshake Step | Memory Action | +|----------------|---------------| +| Client Hello | Allocate TX buffer | +| Server Hello | Allocate RX buffer | +| Server Certificate | Allocate RX buffer | +| Certificate Verify | Allocate TX buffer | +| Free certificate resources | Release CA certificates | +| Client Key Exchange | Allocate TX buffer | +| Change Cipher Spec | Small buffer | +| Finished | Small buffers | + + + +## Implementation Design: SSL Read Operation + +The dynamic buffer allocation for SSL read operations follows this design: + +1. First, read just the TLS record header: + +```c +// Read just the header to determine full message size +ssl->in_hdr = msg_head; +ssl->in_len = msg_head + 3; +mbedtls_ssl_fetch_input(ssl, mbedtls_ssl_in_hdr_len(ssl)); + +// Parse header to get message length +esp_mbedtls_parse_record_header(ssl); +in_msglen = ssl->in_msglen; +``` + +2. Once we know the exact message size, allocate a buffer that's precisely sized: + +```c +// Allocate buffer of right size +buffer_len = in_msglen + overhead; // Add necessary TLS overhead +esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); + +// Initialize and set up buffer +esp_mbedtls_init_ssl_buf(esp_buf, buffer_len); +init_rx_buffer(ssl, esp_buf->buf); +``` + +3. After processing, preserve critical state and free the large buffer: + +```c +// Save critical state (counters and IVs) +memcpy(buf, ssl->in_ctr, 8); +memcpy(buf + 8, ssl->in_iv, 8); + +// Free large buffer +esp_mbedtls_free_buf(ssl->in_buf); + +// Allocate small cache buffer +esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + 16); +esp_mbedtls_init_ssl_buf(esp_buf, 16); + +// Restore critical state in small buffer +memcpy(esp_buf->buf, buf, 16); +``` + +## Configuration Options + +The dynamic buffer system includes configurable options: + +- `CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT`: Free CA certificates after verification +- `CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA`: Free DHM parameters and key material when no longer needed + +These can be enabled in ESP-IDF's menuconfig system. + +## Integration Architecture + +The implementation uses function wrapping to seamlessly integrate with mbedTLS: + +``` +Application → mbedTLS API → Our Wrapper Functions → Original mbedTLS Functions +``` + +Key wrapped functions include: +- `mbedtls_ssl_setup`: Initialize with minimal buffers +- `mbedtls_ssl_read`/`mbedtls_ssl_write`: Dynamic buffer management during I/O +- `mbedtls_ssl_handshake_client_step`: Handshake-aware memory management +- `mbedtls_ssl_free`: Clean up all allocated memory + +## Design Benefits + +The dynamic buffer management design provides several key benefits: + +1. **Memory Efficiency**: Significantly reduced peak memory usage +2. **Scalability**: Adapts to different TLS message sizes dynamically +3. **Transparency**: Application code doesn't need to be aware of the memory optimization +4. **Compatibility**: Maintains full mbedTLS functionality + +## Summary + +The dynamic buffer management system in ESP-IDF's mbedTLS port follows a sophisticated architecture that: +1. Allocates only what's needed, when it's needed +2. Preserves critical state in small buffers +3. Is aware of the TLS handshake flow +4. Releases memory as soon as it's no longer required + +This architecture enables more efficient use of RAM on memory-constrained devices while maintaining the security guarantees of the TLS protocol. diff --git a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c index d76f6b5049..3c9b448271 100644 --- a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c +++ b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -352,6 +352,8 @@ int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl) ESP_LOGD(TAG, "mbedtls_ssl_fetch_input reads data times out"); } else if (ret == MBEDTLS_ERR_SSL_WANT_READ) { ESP_LOGD(TAG, "mbedtls_ssl_fetch_input wants to read more data"); + } else if (ret == MBEDTLS_ERR_SSL_CONN_EOF) { + ESP_LOGD(TAG, "mbedtls_ssl_fetch_input connection EOF"); } else { ESP_LOGE(TAG, "mbedtls_ssl_fetch_input error=%d", -ret); } diff --git a/components/mbedtls/port/dynamic/esp_ssl_cli.c b/components/mbedtls/port/dynamic/esp_ssl_cli.c index 0ea78df59d..376f104780 100644 --- a/components/mbedtls/port/dynamic/esp_ssl_cli.c +++ b/components/mbedtls/port/dynamic/esp_ssl_cli.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,9 +9,11 @@ int __real_mbedtls_ssl_handshake_client_step(mbedtls_ssl_context *ssl); int __real_mbedtls_ssl_write_client_hello(mbedtls_ssl_context *ssl); +int __real_mbedtls_ssl_tls13_handshake_client_step(mbedtls_ssl_context *ssl); int __wrap_mbedtls_ssl_handshake_client_step(mbedtls_ssl_context *ssl); int __wrap_mbedtls_ssl_write_client_hello(mbedtls_ssl_context *ssl); +int __wrap_mbedtls_ssl_tls13_handshake_client_step(mbedtls_ssl_context *ssl); static const char *TAG = "SSL client"; @@ -52,6 +54,15 @@ static int manage_resource(mbedtls_ssl_context *ssl, bool add) case MBEDTLS_SSL_SERVER_HELLO: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + if (!ssl->MBEDTLS_PRIVATE(keep_current_message)) { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + } + break; + case MBEDTLS_SSL_ENCRYPTED_EXTENSIONS: if (add) { CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); } else { @@ -121,6 +132,13 @@ static int manage_resource(mbedtls_ssl_context *ssl, bool add) CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); } break; + case MBEDTLS_SSL_CLIENT_CERTIFICATE_VERIFY: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; case MBEDTLS_SSL_CLIENT_KEY_EXCHANGE: if (add) { size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; @@ -191,6 +209,39 @@ static int manage_resource(mbedtls_ssl_context *ssl, bool add) } #endif break; +#if defined(MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE) + case MBEDTLS_SSL_CLIENT_CCS_BEFORE_2ND_CLIENT_HELLO: + if (add) { + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, MBEDTLS_SSL_OUT_BUFFER_LEN)); + } + break; + case MBEDTLS_SSL_CLIENT_CCS_AFTER_SERVER_FINISHED: + if (add) { + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, MBEDTLS_SSL_OUT_BUFFER_LEN)); + } + break; +#if defined(MBEDTLS_SSL_EARLY_DATA) + case MBEDTLS_SSL_CLIENT_CCS_AFTER_CLIENT_HELLO: + if (add) { + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, MBEDTLS_SSL_OUT_BUFFER_LEN)); + } + break; + case MBEDTLS_SSL_END_OF_EARLY_DATA: + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + break; +#endif /* MBEDTLS_SSL_EARLY_DATA */ +#endif /* MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE */ +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + case MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; +#endif /* MBEDTLS_SSL_SESSION_TICKETS */ default: break; } @@ -209,6 +260,17 @@ int __wrap_mbedtls_ssl_handshake_client_step(mbedtls_ssl_context *ssl) return 0; } +int __wrap_mbedtls_ssl_tls13_handshake_client_step(mbedtls_ssl_context *ssl) +{ + CHECK_OK(manage_resource(ssl, true)); + + CHECK_OK(__real_mbedtls_ssl_tls13_handshake_client_step(ssl)); + + CHECK_OK(manage_resource(ssl, false)); + + return 0; +} + int __wrap_mbedtls_ssl_write_client_hello(mbedtls_ssl_context *ssl) { CHECK_OK(manage_resource(ssl, true)); diff --git a/components/mbedtls/port/dynamic/esp_ssl_tls.c b/components/mbedtls/port/dynamic/esp_ssl_tls.c index e335a27e6b..3b687b4bec 100644 --- a/components/mbedtls/port/dynamic/esp_ssl_tls.c +++ b/components/mbedtls/port/dynamic/esp_ssl_tls.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -347,6 +347,43 @@ int __wrap_mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t ret = __real_mbedtls_ssl_read(ssl, buf, len); +#if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 + /* + * As per RFC 8446, section 4.6.1 the server may send a NewSessionTicket message at any time after the + * client Finished message. + * If a post-handshake message is received, connection state is changed to `MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET` + * and when the message is parsed, the return value is `MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET`. + * When the session ticket is parsed, reduce the ssl->in_msglen by the length of the + * NewSessionTicket message. + */ + if (mbedtls_ssl_get_version_number(ssl) == MBEDTLS_SSL_VERSION_TLS1_3) { + if (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) { + ESP_LOGD(TAG, "got session ticket in TLS 1.3 connection, retry read"); + + /* At this stage, we have received a NewSessionTicket messages + * We should decrement the ssl->in_msglen by the length of the + * NewSessionTicket message. + * + * The NewSessionTicket message has been parsed internally by mbedTLS + * and it is stored in the mbedTLS context. This msglen size update + * is also handled by mbedTLS but in case of dynamic buffer, + * we need to free the rx buffer if it is allocated + * and prepare for the next read. So we have to update the msglen + * by ourselves and free the rx buffer if no more data is available. + */ + if (ssl->MBEDTLS_PRIVATE(in_hslen) < ssl->MBEDTLS_PRIVATE(in_msglen)) { + ssl->MBEDTLS_PRIVATE(in_msglen) -= ssl->MBEDTLS_PRIVATE(in_hslen); + memmove(ssl->MBEDTLS_PRIVATE(in_msg), ssl->MBEDTLS_PRIVATE(in_msg) + ssl->MBEDTLS_PRIVATE(in_hslen), + ssl->MBEDTLS_PRIVATE(in_msglen)); + MBEDTLS_PUT_UINT16_BE(ssl->MBEDTLS_PRIVATE(in_msglen), ssl->in_len, 0); + } else { + ssl->MBEDTLS_PRIVATE(in_msglen) = 0; + } + ssl->MBEDTLS_PRIVATE(in_hslen) = 0; + } + } +#endif // CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 + if (rx_done(ssl)) { CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); } diff --git a/examples/protocols/https_request/main/Kconfig.projbuild b/examples/protocols/https_request/main/Kconfig.projbuild index 60a72c61dd..6665bfb6e6 100644 --- a/examples/protocols/https_request/main/Kconfig.projbuild +++ b/examples/protocols/https_request/main/Kconfig.projbuild @@ -23,4 +23,11 @@ menu "Example Configuration" bool default y if EXAMPLE_LOCAL_SERVER_URL = "FROM_STDIN" + config EXAMPLE_SSL_PROTO_TLS1_3_CLIENT + bool "Enable TLS 1.3 client test" + default n + select MBEDTLS_SSL_PROTO_TLS1_3 + help + Enable TLS 1.3 client test support for the example. + endmenu diff --git a/examples/protocols/https_request/main/https_request_example_main.c b/examples/protocols/https_request/main/https_request_example_main.c index 774aa62fbe..abbb050ac7 100644 --- a/examples/protocols/https_request/main/https_request_example_main.c +++ b/examples/protocols/https_request/main/https_request_example_main.c @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: Apache-2.0 * - * SPDX-FileContributor: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2015-2025 Espressif Systems (Shanghai) CO LTD */ #include @@ -46,9 +46,15 @@ #include "time_sync.h" /* Constants that aren't configurable in menuconfig */ -#define WEB_SERVER "www.howsmyssl.com" +#ifdef CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT +#define WEB_SERVER "tls13.browserleaks.com" +#define WEB_PORT "443" +#define WEB_URL "https://tls13.browserleaks.com/tls" +#else +#define WEB_SERVER "howsmyssl.com" #define WEB_PORT "443" #define WEB_URL "https://www.howsmyssl.com/a/check" +#endif #define SERVER_URL_MAX_SZ 256 @@ -85,9 +91,15 @@ extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_ extern const uint8_t local_server_cert_pem_start[] asm("_binary_local_server_cert_pem_start"); extern const uint8_t local_server_cert_pem_end[] asm("_binary_local_server_cert_pem_end"); #if CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS +#if defined(CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT) +static const int server_supported_ciphersuites[] = {MBEDTLS_TLS1_3_AES_256_GCM_SHA384, MBEDTLS_TLS1_3_AES_128_CCM_SHA256, 0}; +static const int server_unsupported_ciphersuites[] = {MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256, 0}; +#else static const int server_supported_ciphersuites[] = {MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384, MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, 0}; static const int server_unsupported_ciphersuites[] = {MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256, 0}; -#endif +#endif // CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT +#endif // CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS + #ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS static esp_tls_client_session_t *tls_client_session = NULL; static bool save_client_session = false; @@ -119,14 +131,6 @@ static void https_get_request(esp_tls_cfg_t cfg, const char *WEB_SERVER_URL, con goto cleanup; } -#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS - /* The TLS session is successfully established, now saving the session ctx for reuse */ - if (save_client_session) { - esp_tls_free_client_session(tls_client_session); - tls_client_session = esp_tls_get_client_session(tls); - } -#endif - size_t written_bytes = 0; do { ret = esp_tls_conn_write(tls, @@ -166,6 +170,14 @@ static void https_get_request(esp_tls_cfg_t cfg, const char *WEB_SERVER_URL, con putchar('\n'); // JSON output doesn't have a newline at end } while (1); +#ifdef CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS + /* The TLS session is successfully established, now saving the session ctx for reuse */ + if (save_client_session) { + esp_tls_free_client_session(tls_client_session); + tls_client_session = esp_tls_get_client_session(tls); + } +#endif + cleanup: esp_tls_conn_destroy(tls); exit: @@ -251,6 +263,9 @@ static void https_get_request_using_already_saved_session(const char *url) ESP_LOGI(TAG, "https_request using saved client session"); esp_tls_cfg_t cfg = { .client_session = tls_client_session, + .cacert_buf = (const unsigned char *) local_server_cert_pem_start, + .cacert_bytes = local_server_cert_pem_end - local_server_cert_pem_start, + .skip_common_name = true, }; https_get_request(cfg, url, LOCAL_SRV_REQUEST); esp_tls_free_client_session(tls_client_session); diff --git a/examples/protocols/https_request/pytest_https_request.py b/examples/protocols/https_request/pytest_https_request.py index 456c5ede88..bdceb7baad 100644 --- a/examples/protocols/https_request/pytest_https_request.py +++ b/examples/protocols/https_request/pytest_https_request.py @@ -125,6 +125,90 @@ def test_examples_protocol_https_request_cli_session_tickets(dut: Dut) -> None: thread1.terminate() +@pytest.mark.ethernet +@pytest.mark.parametrize( + 'config', + [ + 'ssldyn_tls1_3', + ], + indirect=True, +) +@pytest.mark.parametrize('erase_nvs', ['y'], indirect=True) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_examples_protocol_https_request_dynamic_buffers_tls1_3(dut: Dut) -> None: + # Check for tls 1.3 connection using crt bundle with mbedtls dynamic resource enabled + # check and log bin size + binary_file = os.path.join(dut.app.binary_path, 'https_request.bin') + bin_size = os.path.getsize(binary_file) + logging.info('https_request_bin_size : {}KB'.format(bin_size // 1024)) + # start https server + server_port = 8070 + server_file = os.path.join(os.path.dirname(__file__), 'main', 'local_server_cert.pem') + key_file = os.path.join(os.path.dirname(__file__), 'main', 'local_server_key.pem') + thread1 = multiprocessing.Process(target=start_https_server, args=(server_file, key_file, '0.0.0.0', server_port)) + thread1.daemon = True + thread1.start() + logging.info('The server started on localhost:{}'.format(server_port)) + + dut.expect('Loaded app from partition at offset', timeout=30) + try: + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode() + print('Connected to AP/Ethernet with IP: {}'.format(ip_address)) + host_ip = get_host_ip4_by_dest_ip(ip_address) + dut.expect('Start https_request example', timeout=30) + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port))) + dut.write('https://' + host_ip + ':' + str(server_port)) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + # Check for connection using already saved client session + try: + dut.expect('https_request to local server', timeout=30) + dut.expect( + ['Connection established...', 'Reading HTTP response...', 'HTTP/1.1 200 OK', 'connection closed'], + expect_all=True, + ) + except Exception: + logging.info('Failed to connect to local https server"') + raise + + try: + dut.expect('https_request using saved client session', timeout=20) + dut.expect( + ['Connection established...', 'Reading HTTP response...', 'HTTP/1.1 200 OK', 'connection closed'], + expect_all=True, + ) + except Exception: + logging.info('Failed the test for "https_request using saved client session"') + raise + # only check if one connection is established + logging.info('Testing for "https_request using crt bundle" with mbedtls dynamic resource enabled') + try: + dut.expect('https_request using crt bundle', timeout=30) + dut.expect( + [ + 'Connection established...', + 'Reading HTTP response...', + 'HTTP/1.1 200 OK', + 'TLS 1.3', + 'connection closed', + ], + expect_all=True, + ) + except Exception: + logging.info( + 'Failed the test for "https_request using crt bundle" with TLS 1.3 ' + 'when mbedtls dynamic resource was enabled' + ) + raise + logging.info( + 'Passed the test for "https_request using crt bundle" with TLS 1.3 when ' + 'mbedtls dynamic resource was enabled' + ) + finally: + thread1.terminate() + + @pytest.mark.ethernet @pytest.mark.parametrize( 'config', diff --git a/examples/protocols/https_request/sdkconfig.ci.ssldyn_tls1_3 b/examples/protocols/https_request/sdkconfig.ci.ssldyn_tls1_3 new file mode 100644 index 0000000000..0e198f8102 --- /dev/null +++ b/examples/protocols/https_request/sdkconfig.ci.ssldyn_tls1_3 @@ -0,0 +1,15 @@ +CONFIG_SPIRAM=y +CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y +CONFIG_EXAMPLE_SSL_PROTO_TLS1_3_CLIENT=y +CONFIG_EXAMPLE_CLIENT_SESSION_TICKETS=y +CONFIG_EXAMPLE_LOCAL_SERVER_URL="FROM_STDIN" +CONFIG_EXAMPLE_LOCAL_SERVER_URL_FROM_STDIN=y diff --git a/examples/system/ota/simple_ota_example/pytest_simple_ota.py b/examples/system/ota/simple_ota_example/pytest_simple_ota.py index e3ab7cd8f0..2a925fab78 100644 --- a/examples/system/ota/simple_ota_example/pytest_simple_ota.py +++ b/examples/system/ota/simple_ota_example/pytest_simple_ota.py @@ -391,6 +391,7 @@ def test_examples_protocol_simple_ota_example_with_verify_app_signature_on_updat 'config', [ 'tls1_3', + 'tls1_3_only_dynamic', ], indirect=True, ) diff --git a/examples/system/ota/simple_ota_example/sdkconfig.ci.tls1_3_only_dynamic b/examples/system/ota/simple_ota_example/sdkconfig.ci.tls1_3_only_dynamic new file mode 100644 index 0000000000..c48709218e --- /dev/null +++ b/examples/system/ota/simple_ota_example/sdkconfig.ci.tls1_3_only_dynamic @@ -0,0 +1,13 @@ +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y