Merge branch 'feat/dynamic_buffer_tls1.3' into 'master'

feat(mbedtls): add support for dynamic buffer for TLS1.3

Closes IDFGH-14708, IDF-12469, IDF-9178, and IDF-1725

See merge request espressif/esp-idf!38258
This commit is contained in:
Mahavir Jain
2025-04-30 17:52:43 +08:00
15 changed files with 589 additions and 24 deletions

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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));

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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 <string.h>
@@ -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);

View File

@@ -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',

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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