Skip to content

Commit 02af1db

Browse files
committed
Merge branch 'feat(esp-tls)/add_more_server_configurations' into 'master'
feat(esp_tls): supports setting tls version and ciphersuite in server config Closes IDFGH-16537 See merge request espressif/esp-idf!42323
2 parents 51d5e8f + 62f852a commit 02af1db

File tree

9 files changed

+244
-21
lines changed

9 files changed

+244
-21
lines changed

components/esp-tls/esp_tls.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ typedef struct esp_tls_cfg_server {
364364
Important note: the pointer must be valid for connection */
365365
#endif
366366

367+
esp_tls_proto_ver_t tls_version; /*!< TLS protocol version for this server, e.g., TLS 1.2, TLS 1.3
368+
(default - no preference). Enables TLS version control per server instance. */
369+
370+
const int *ciphersuites_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites.
371+
Please check the list validity by esp_tls_get_ciphersuites_list() API.
372+
This allows per-server cipher suite configuration. */
367373
} esp_tls_cfg_server_t;
368374

369375
/**

components/esp-tls/esp_tls_mbedtls.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,29 @@ static esp_err_t set_server_config(esp_tls_cfg_server_t *cfg, esp_tls_t *tls)
868868
}
869869
#endif
870870

871+
// Configure per-service TLS version
872+
const esp_tls_proto_ver_t tls_ver = cfg->tls_version;
873+
if (tls_ver == ESP_TLS_VER_TLS_1_3) {
874+
#if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3
875+
ESP_LOGI(TAG, "Setting server TLS version to 0x%4x", MBEDTLS_SSL_VERSION_TLS1_3);
876+
mbedtls_ssl_conf_min_tls_version(&tls->conf, MBEDTLS_SSL_VERSION_TLS1_3);
877+
mbedtls_ssl_conf_max_tls_version(&tls->conf, MBEDTLS_SSL_VERSION_TLS1_3);
878+
#else
879+
ESP_LOGE(TAG, "TLS 1.3 is not enabled in config");
880+
return ESP_ERR_INVALID_ARG;
881+
#endif
882+
} else if (tls_ver == ESP_TLS_VER_TLS_1_2) {
883+
ESP_LOGD(TAG, "Setting server TLS version to 0x%4x", MBEDTLS_SSL_VERSION_TLS1_2);
884+
mbedtls_ssl_conf_min_tls_version(&tls->conf, MBEDTLS_SSL_VERSION_TLS1_2);
885+
mbedtls_ssl_conf_max_tls_version(&tls->conf, MBEDTLS_SSL_VERSION_TLS1_2);
886+
}
887+
888+
if (cfg->ciphersuites_list != NULL && cfg->ciphersuites_list[0] != 0) {
889+
ESP_LOGD(TAG, "Set the server ciphersuites list (user-provided)");
890+
mbedtls_ssl_conf_ciphersuites(&tls->conf, cfg->ciphersuites_list);
891+
} else {
892+
ESP_LOGD(TAG, "No custom cipher suites provided - using default");
893+
}
871894
return ESP_OK;
872895
}
873896

components/esp_https_server/include/esp_https_server.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ struct httpd_ssl_config {
141141

142142
/** TLS handshake timeout in milliseconds, default timeout is 10 seconds if not set */
143143
uint32_t tls_handshake_timeout_ms;
144+
145+
/** TLS protocol version for this server, e.g., TLS 1.2, TLS 1.3
146+
* (default - no preference). Enables per-server TLS version control. */
147+
esp_tls_proto_ver_t tls_version;
148+
149+
/** Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites.
150+
* Please check the list validity by esp_tls_get_ciphersuites_list() API.
151+
* This allows per-server cipher suite configuration. */
152+
const int *ciphersuites_list;
144153
};
145154

146155
typedef struct httpd_ssl_config httpd_ssl_config_t;
@@ -203,7 +212,9 @@ typedef struct httpd_ssl_config httpd_ssl_config_t;
203212
.ssl_userdata = NULL, \
204213
.cert_select_cb = NULL, \
205214
.alpn_protos = NULL, \
206-
.tls_handshake_timeout_ms = 0 \
215+
.tls_handshake_timeout_ms = 0, \
216+
.tls_version = ESP_TLS_VER_ANY, \
217+
.ciphersuites_list = NULL, \
207218
}
208219

209220
/**

components/esp_https_server/src/https_server.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ static esp_err_t create_secure_context(const struct httpd_ssl_config *config, ht
279279
cfg->alpn_protos = config->alpn_protos;
280280
cfg->tls_handshake_timeout_ms = config->tls_handshake_timeout_ms;
281281

282+
cfg->tls_version = config->tls_version;
283+
cfg->ciphersuites_list = config->ciphersuites_list;
284+
282285
#if defined(CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK)
283286
cfg->cert_select_cb = config->cert_select_cb;
284287
#endif

examples/protocols/https_server/simple/main/Kconfig.projbuild

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,31 @@ menu "Example Configuration"
77
Enable user callback for esp_https_server which can be used to get SSL context (connection information)
88
E.g. Certificate of the connected client
99

10+
choice EXAMPLE_ENABLE_HTTPS_SERVER_TLS_VERSION
11+
prompt "Choose TLS version"
12+
default EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_2_ONLY
13+
config EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_2_ONLY
14+
bool "TLS 1.2"
15+
select MBEDTLS_SSL_PROTO_TLS1_2
16+
help
17+
Enable HTTPS server TLS 1.2
18+
config EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_3_ONLY
19+
bool "TLS 1.3"
20+
select MBEDTLS_SSL_PROTO_TLS1_3
21+
help
22+
Enable HTTPS server TLS 1.3
23+
config EXAMPLE_ENABLE_HTTPS_SERVER_TLS_ANY
24+
bool "TLS 1.3 and 1.2"
25+
select MBEDTLS_SSL_PROTO_TLS1_3
26+
select MBEDTLS_SSL_PROTO_TLS1_2
27+
help
28+
Enable HTTPS server TLS 1.3 and 1.2
29+
endchoice
30+
31+
config EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
32+
bool "Enable HTTPS server custom ciphersuites"
33+
default n
34+
help
35+
Enable HTTPS server custom ciphersuites
36+
1037
endmenu

examples/protocols/https_server/simple/main/main.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#include "esp_tls.h"
2222
#include "sdkconfig.h"
2323

24+
#if CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
25+
#include "mbedtls/ssl_ciphersuites.h"
26+
#endif // CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
27+
2428
/* A simple example that demonstrates how to create GET and POST
2529
* handlers and start an HTTPS server.
2630
*/
@@ -159,6 +163,27 @@ static httpd_handle_t start_webserver(void)
159163
conf.prvtkey_pem = prvtkey_pem_start;
160164
conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;
161165

166+
#if CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
167+
static const int ciphersuites_to_use[] = {
168+
MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
169+
MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
170+
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
171+
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
172+
0,
173+
};
174+
conf.ciphersuites_list = ciphersuites_to_use;
175+
#else
176+
conf.ciphersuites_list = NULL;
177+
#endif // CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
178+
179+
#if CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_3_ONLY
180+
conf.tls_version = ESP_TLS_VER_TLS_1_3;
181+
#elif CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_2_ONLY
182+
conf.tls_version = ESP_TLS_VER_TLS_1_2;
183+
#else
184+
conf.tls_version = ESP_TLS_VER_ANY;
185+
#endif // CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_3_ONLY
186+
162187
#if CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK
163188
conf.user_cb = https_server_user_callback;
164189
#endif

examples/protocols/https_server/simple/pytest_https_server_simple.py

Lines changed: 142 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def test_examples_protocol_https_server_simple(dut: Dut) -> None:
109109
# check and log bin size
110110
binary_file = os.path.join(dut.app.binary_path, 'https_server.bin')
111111
bin_size = os.path.getsize(binary_file)
112-
logging.info('https_server_simple_bin_size : {}KB'.format(bin_size // 1024))
112+
logging.info(f'https_server_simple_bin_size : {bin_size // 1024}KB')
113113
# start test
114114
logging.info('Waiting to connect with AP')
115115
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
@@ -125,8 +125,8 @@ def test_examples_protocol_https_server_simple(dut: Dut) -> None:
125125

126126
# Expected logs
127127

128-
logging.info('Got IP : {}'.format(got_ip))
129-
logging.info('Got Port : {}'.format(got_port))
128+
logging.info(f'Got IP : {got_ip}')
129+
logging.info(f'Got Port : {got_port}')
130130

131131
logging.info('Performing GET request over an SSL connection with the server')
132132

@@ -156,7 +156,7 @@ def test_examples_protocol_https_server_simple(dut: Dut) -> None:
156156

157157
if dut.app.sdkconfig.get('CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
158158
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
159-
logging.info('Current Ciphersuite {}'.format(current_cipher))
159+
logging.info(f'Current Ciphersuite {current_cipher}')
160160

161161
logging.info('Checking user callback: Obtaining client certificate...')
162162

@@ -166,9 +166,9 @@ def test_examples_protocol_https_server_simple(dut: Dut) -> None:
166166
1
167167
].decode()
168168

169-
logging.info('Serial No. {}'.format(serial_number))
170-
logging.info('Issuer Name {}'.format(issuer_name))
171-
logging.info('Expires on {}'.format(expiry))
169+
logging.info(f'Serial No. {serial_number}')
170+
logging.info(f'Issuer Name {issuer_name}')
171+
logging.info(f'Expires on {expiry}')
172172

173173
# Close the connection
174174
conn.close()
@@ -203,8 +203,8 @@ def test_examples_protocol_https_server_simple_dynamic_buffers(dut: Dut) -> None
203203

204204
# Expected logs
205205

206-
logging.info('Got IP : {}'.format(got_ip))
207-
logging.info('Got Port : {}'.format(got_port))
206+
logging.info(f'Got IP : {got_ip}')
207+
logging.info(f'Got Port : {got_port}')
208208

209209
logging.info('Performing GET request over an SSL connection with the server')
210210

@@ -233,7 +233,7 @@ def test_examples_protocol_https_server_simple_dynamic_buffers(dut: Dut) -> None
233233

234234
if dut.app.sdkconfig.get('CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
235235
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
236-
logging.info('Current Ciphersuite {}'.format(current_cipher))
236+
logging.info(f'Current Ciphersuite {current_cipher}')
237237

238238
logging.info('Checking user callback: Obtaining client certificate...')
239239

@@ -243,9 +243,9 @@ def test_examples_protocol_https_server_simple_dynamic_buffers(dut: Dut) -> None
243243
1
244244
].decode()
245245

246-
logging.info('Serial No. : {}'.format(serial_number))
247-
logging.info('Issuer Name : {}'.format(issuer_name))
248-
logging.info('Expires on : {}'.format(expiry))
246+
logging.info(f'Serial No. : {serial_number}')
247+
logging.info(f'Issuer Name : {issuer_name}')
248+
logging.info(f'Expires on : {expiry}')
249249

250250
# Close the connection
251251
conn.close()
@@ -277,8 +277,8 @@ def test_examples_protocol_https_server_tls1_3(dut: Dut) -> None:
277277

278278
# Expected logs
279279

280-
logging.info('Got IP : {}'.format(got_ip))
281-
logging.info('Got Port : {}'.format(got_port))
280+
logging.info(f'Got IP : {got_ip}')
281+
logging.info(f'Got Port : {got_port}')
282282
logging.info('Performing GET request over an SSL connection with the server using TLSv1.3')
283283

284284
CLIENT_CERT_FILE = 'client_cert.pem'
@@ -288,14 +288,136 @@ def test_examples_protocol_https_server_tls1_3(dut: Dut) -> None:
288288
cert.write(client_cert_pem)
289289
key.write(client_key_pem)
290290

291+
# First try with TLSv1.2 and that should fail
292+
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
293+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
294+
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_2
295+
ssl_context.verify_mode = ssl.CERT_REQUIRED
296+
ssl_context.check_hostname = False
297+
ssl_context.load_verify_locations(cadata=server_cert_pem)
298+
ssl_context.load_cert_chain(certfile=CLIENT_CERT_FILE, keyfile=CLIENT_KEY_FILE)
299+
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
300+
try:
301+
conn.request('GET', '/')
302+
except ssl.SSLError as e:
303+
logging.info(f'SSL handshake failed with TLSv1.2: {e}')
304+
else:
305+
logging.info('SSL handshake succeeded with TLSv1.2')
306+
raise RuntimeError('This should have failed')
307+
308+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_3
309+
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_3
310+
311+
os.remove(CLIENT_CERT_FILE)
312+
os.remove(CLIENT_KEY_FILE)
313+
314+
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
315+
logging.info('Performing SSL handshake with the server')
316+
conn.request('GET', '/')
317+
resp = conn.getresponse()
318+
dut.expect('performing session handshake')
319+
got_resp = resp.read().decode('utf-8')
320+
if got_resp != success_response:
321+
logging.info('Response obtained does not match with correct response')
322+
raise RuntimeError('Failed to test SSL connection')
323+
324+
if dut.app.sdkconfig.get('CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
325+
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
326+
logging.info(f'Current Ciphersuite {current_cipher}')
327+
328+
logging.info('Checking user callback: Obtaining client certificate...')
329+
330+
serial_number = dut.expect(r'serial number\s*:([^\n]*)', timeout=5)[0]
331+
issuer_name = dut.expect(r'issuer name\s*:([^\n]*)', timeout=5)[0]
332+
expiry = dut.expect(
333+
r'expires on\s*:((.*)\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])*)',
334+
timeout=5,
335+
)[1].decode()
336+
337+
logging.info(f'Serial No. : {serial_number}')
338+
logging.info(f'Issuer Name : {issuer_name}')
339+
logging.info(f'Expires on : {expiry}')
340+
341+
# Close the connection
342+
conn.close()
343+
logging.info('Correct response obtained')
344+
logging.info('SSL connection test successful\nClosing the connection')
345+
346+
347+
@pytest.mark.wifi_router
348+
@pytest.mark.parametrize(
349+
'config',
350+
[
351+
'tls1_2_only',
352+
],
353+
indirect=True,
354+
)
355+
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3'], indirect=['target'])
356+
def test_examples_protocol_https_server_tls1_2_only(dut: Dut) -> None:
357+
logging.info('Waiting to connect with AP')
358+
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
359+
dut.expect('Please input ssid password:')
360+
env_name = 'wifi_router'
361+
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
362+
ap_password = get_env_config_variable(env_name, 'ap_password')
363+
dut.write(f'{ap_ssid} {ap_password}')
364+
# Parse IP address and port of the server
365+
dut.expect(r'Starting server')
366+
got_port = int(dut.expect(r'Server listening on port (\d+)', timeout=30)[1].decode())
367+
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode()
368+
369+
# Expected logs
370+
logging.info(f'Got IP : {got_ip}')
371+
logging.info(f'Got Port : {got_port}')
372+
logging.info('Performing GET request over an SSL connection with the server using TLSv1.2')
373+
374+
CLIENT_CERT_FILE = 'client_cert.pem'
375+
CLIENT_KEY_FILE = 'client_key.pem'
376+
377+
with open(CLIENT_CERT_FILE, 'w', encoding='utf-8') as cert, open(CLIENT_KEY_FILE, 'w', encoding='utf-8') as key:
378+
cert.write(client_cert_pem)
379+
key.write(client_key_pem)
380+
381+
# First try with TLSv1.3 and that should fail
291382
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
292383
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_3
293384
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_3
294385
ssl_context.verify_mode = ssl.CERT_REQUIRED
295386
ssl_context.check_hostname = False
296387
ssl_context.load_verify_locations(cadata=server_cert_pem)
297-
298388
ssl_context.load_cert_chain(certfile=CLIENT_CERT_FILE, keyfile=CLIENT_KEY_FILE)
389+
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
390+
try:
391+
conn.request('GET', '/')
392+
except ssl.SSLError as e:
393+
logging.info(f'SSL handshake failed with TLSv1.3: {e}')
394+
else:
395+
logging.info('SSL handshake succeeded with TLSv1.3')
396+
raise RuntimeError('This should have failed')
397+
398+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
399+
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_2
400+
401+
# Also now with TLS1.2, try with a non matching ciphersuite and that should fail
402+
# Server only accepts: DHE-RSA-AES128-SHA256, DHE-RSA-AES256-SHA256,
403+
# ECDHE-RSA-AES256-SHA384, ECDHE-RSA-AES128-SHA256
404+
# Try AES128-GCM-SHA256 which is NOT in the list
405+
ssl_context.set_ciphers('AES128-GCM-SHA256')
406+
407+
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
408+
try:
409+
logging.info('Trying SSL handshake with non-matching ciphersuite (should fail)')
410+
conn.request('GET', '/')
411+
except ssl.SSLError as e:
412+
logging.info(f'SSL handshake failed with non-matching ciphersuite (expected): {e}')
413+
else:
414+
logging.info('SSL handshake succeeded with non-matching ciphersuite')
415+
raise RuntimeError('This should have failed - custom ciphersuites not enforced')
416+
finally:
417+
conn.close()
418+
419+
# Now try with the matching ciphersuite
420+
ssl_context.set_ciphers('DHE-RSA-AES128-SHA256')
299421

300422
os.remove(CLIENT_CERT_FILE)
301423
os.remove(CLIENT_KEY_FILE)
@@ -312,7 +434,7 @@ def test_examples_protocol_https_server_tls1_3(dut: Dut) -> None:
312434

313435
if dut.app.sdkconfig.get('CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
314436
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
315-
logging.info('Current Ciphersuite {}'.format(current_cipher))
437+
logging.info(f'Current Ciphersuite {current_cipher}')
316438

317439
logging.info('Checking user callback: Obtaining client certificate...')
318440

@@ -323,9 +445,9 @@ def test_examples_protocol_https_server_tls1_3(dut: Dut) -> None:
323445
timeout=5,
324446
)[1].decode()
325447

326-
logging.info('Serial No. : {}'.format(serial_number))
327-
logging.info('Issuer Name : {}'.format(issuer_name))
328-
logging.info('Expires on : {}'.format(expiry))
448+
logging.info(f'Serial No. : {serial_number}')
449+
logging.info(f'Issuer Name : {issuer_name}')
450+
logging.info(f'Expires on : {expiry}')
329451

330452
# Close the connection
331453
conn.close()

0 commit comments

Comments
 (0)