diff --git a/arch/esp32/extra_scripts/generate_web_panel_cert.py b/arch/esp32/extra_scripts/generate_web_panel_cert.py index 37b07577..80e605a2 100644 --- a/arch/esp32/extra_scripts/generate_web_panel_cert.py +++ b/arch/esp32/extra_scripts/generate_web_panel_cert.py @@ -29,7 +29,7 @@ with tempfile.TemporaryDirectory() as tmp: try: subprocess.run( - ["openssl", "ecparam", "-name", "prime256v1", "-genkey", "-noout", "-out", str(key_path)], + ["openssl", "genrsa", "-out", str(key_path), "2048"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/arch/esp32/tls_cipher_restrict.c b/arch/esp32/tls_cipher_restrict.c new file mode 100644 index 00000000..4b062b53 --- /dev/null +++ b/arch/esp32/tls_cipher_restrict.c @@ -0,0 +1,76 @@ +/* + * arch/esp32/tls_cipher_restrict.c + * + * Restricts TLS server cipher suites to RSA key exchange, eliminating + * the ECDH computation that starves IDLE0 and triggers the task watchdog + * during HTTPS handshakes. + * + * Background: + * Browsers negotiate ECDHE cipher suites by default. On ESP32-S3 the + * hardware RSA accelerator handles RSA key exchange efficiently, but + * there is no ECP hardware accelerator. ECDHE requires the server to + * compute an ephemeral key pair: ecp_precompute_comb() builds a comb + * table through many sequential ECP point doublings, each dispatched + * to the hardware bignum unit (esp_bignum.c), but the ECP layer has + * no RTOS yield points between iterations. The entire computation runs + * to completion on CPU 0 without ever resetting the task watchdog. + * + * A single handshake does not exceed the watchdog timeout on its own, + * but two consecutive handshakes (e.g. a browser retry after a failed + * attempt) accumulate enough uninterrupted runtime to starve IDLE0: + * + * E (54924) esp-tls-mbedtls: mbedtls_ssl_handshake returned -0x0050 + * E (57208) esp-tls-mbedtls: mbedtls_ssl_handshake returned -0x7280 + * E (57638) task_wdt: Task watchdog got triggered. + * E (57638) task_wdt: - IDLE0 (CPU 0) + * E (57638) task_wdt: Tasks currently running: + * E (57638) task_wdt: CPU 0: httpd + * + * The crash occurs in ecp_precompute_comb() → ecp_double_jac() → + * mbedtls_mpi_mul_mpi() during the ServerKeyExchange step. + * + * There is no sdkconfig knob accessible at runtime through + * esp_https_server. The user_cb hook fires after the handshake, too + * late to change cipher suites. The only pre-handshake intercept point + * is mbedtls_ssl_config_defaults(), called once per ssl_config init. + * + * MQTT uses MBEDTLS_SSL_IS_CLIENT; the HTTPS server uses + * MBEDTLS_SSL_IS_SERVER — this is the discriminator. + * + * Note: this file is compiled only for [esp32_base] targets (IDF v4). + */ + +#include "mbedtls/ssl.h" + +/* + * RSA key exchange cipher suites only. + * Must be static — mbedTLS stores the pointer, does not copy the array. + * Terminated with 0. + */ +static const int kServerOnlyCipherSuites[] = { + MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA256, + 0 +}; + +extern int __real_mbedtls_ssl_config_defaults( + mbedtls_ssl_config *conf, + int endpoint, + int transport, + int preset +); + +int __wrap_mbedtls_ssl_config_defaults( + mbedtls_ssl_config *conf, + int endpoint, + int transport, + int preset +) { + int ret = __real_mbedtls_ssl_config_defaults(conf, endpoint, transport, preset); + if (ret == 0 && endpoint == MBEDTLS_SSL_IS_SERVER) { + mbedtls_ssl_conf_ciphersuites(conf, kServerOnlyCipherSuites); + } + return ret; +} diff --git a/platformio.ini b/platformio.ini index 976213bf..2d4e468d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -65,8 +65,10 @@ build_flags = ${arduino_base.build_flags} -D ESP32_PLATFORM ; -D ESP32_CPU_FREQ=80 ; change it to your need -Wl,--wrap=xTaskCreatePinnedToCore + -Wl,--wrap=mbedtls_ssl_config_defaults build_src_filter = ${arduino_base.build_src_filter} +<../arch/esp32/task_pinning.c> + +<../arch/esp32/tls_cipher_restrict.c> [esp32_ota] lib_deps =