mirror of
https://github.com/esphome/esphome.git
synced 2026-08-31 01:56:01 +00:00
Disable mbedTLS TLS and TLS-only crypto when no component needs them
This commit is contained in:
@@ -6,7 +6,7 @@ import esphome.codegen as cg
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
include_builtin_idf_component,
|
||||
request_http_client,
|
||||
require_certificate_bundle,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
@@ -334,8 +334,7 @@ def _emit_memory_pair(value: str | None, psram_key: str, internal_key: str) -> N
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
# Re-enable ESP-IDF's HTTP client (excluded by default to save compile time)
|
||||
include_builtin_idf_component("esp_http_client")
|
||||
request_http_client()
|
||||
# HTTPS streams verify the server against the root certificate bundle
|
||||
require_certificate_bundle()
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ from .const import (
|
||||
KEY_FLASH_SIZE,
|
||||
KEY_FULL_CERT_BUNDLE,
|
||||
KEY_IDF_VERSION,
|
||||
KEY_MBEDTLS_SDKCONFIG,
|
||||
KEY_NETWORK_SDKCONFIG,
|
||||
KEY_PATH,
|
||||
KEY_REF,
|
||||
@@ -218,7 +219,7 @@ DEFAULT_EXCLUDED_IDF_COMPONENTS = (
|
||||
"cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing
|
||||
"console", # Console REPL - unused by ESPHome; espressif/mdns pulls it back when configured
|
||||
"driver", # Legacy driver shim - only needed by esp32_touch, esp32_can for legacy headers
|
||||
"esp-tls", # TLS wrapper - re-included by http_request, mqtt, web_server_idf
|
||||
"esp-tls", # TLS wrapper - re-included by request_tls()
|
||||
"esp_adc", # ADC driver - only needed by adc component
|
||||
"esp_coex", # WiFi/BT coexistence - re-included by esp32_ble_tracker, zigbee; esp_wifi/bt pull it back
|
||||
"esp_driver_cam", # Camera driver - the esp32-camera managed component pulls it back
|
||||
@@ -740,6 +741,48 @@ def request_software_coexistence() -> None:
|
||||
include_builtin_idf_component("esp_coex")
|
||||
|
||||
|
||||
@dataclass
|
||||
class MbedtlsSdkconfigData:
|
||||
"""Inputs for the mbedTLS sdkconfig flags, reconciled at FINAL.
|
||||
|
||||
Components call request_tls() / require_mbedtls_*() instead of writing the
|
||||
CONFIG_MBEDTLS_* flags directly; _reconcile_mbedtls_sdkconfig() decides the
|
||||
final values once every to_code has run.
|
||||
"""
|
||||
|
||||
tls_required: bool = False # TLS/DTLS handshake user
|
||||
ecp_required: bool = False # ECDH/ECDSA without TLS (openthread SRP host key)
|
||||
peer_cert_required: bool = False # keep the peer certificate after the handshake
|
||||
pkcs7_required: bool = False # PKCS#7 parsing
|
||||
sha512_required: bool = False # SHA-384/SHA-512
|
||||
# esp32 advanced disable_mbedtls_peer_cert / disable_mbedtls_pkcs7 options
|
||||
disable_peer_cert: bool = True
|
||||
disable_pkcs7: bool = True
|
||||
|
||||
|
||||
def _mbedtls_sdkconfig() -> MbedtlsSdkconfigData:
|
||||
data = CORE.data[KEY_ESP32]
|
||||
if KEY_MBEDTLS_SDKCONFIG not in data:
|
||||
data[KEY_MBEDTLS_SDKCONFIG] = MbedtlsSdkconfigData()
|
||||
return data[KEY_MBEDTLS_SDKCONFIG]
|
||||
|
||||
|
||||
def request_tls() -> None:
|
||||
"""Request the mbedTLS TLS stack and the esp-tls wrapper.
|
||||
|
||||
Without a request TLS and its ECP/PEM-write/CRL/CSR crypto compile out;
|
||||
hashes, AES and RSA stay available.
|
||||
"""
|
||||
_mbedtls_sdkconfig().tls_required = True
|
||||
include_builtin_idf_component("esp-tls")
|
||||
|
||||
|
||||
def request_http_client() -> None:
|
||||
"""Request ESP-IDF's HTTP client; it links esp_tls even for plain http."""
|
||||
include_builtin_idf_component("esp_http_client")
|
||||
request_tls()
|
||||
|
||||
|
||||
def add_idf_component(
|
||||
*,
|
||||
name: str,
|
||||
@@ -1737,10 +1780,7 @@ KEY_VFS_TERMIOS_REQUIRED = "vfs_termios_required"
|
||||
# Feature requirement tracking - components can call require_* functions to re-enable
|
||||
# These are stored in CORE.data[KEY_ESP32] dict
|
||||
KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED = "usb_serial_jtag_secondary_required"
|
||||
KEY_MBEDTLS_PEER_CERT_REQUIRED = "mbedtls_peer_cert_required"
|
||||
KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required"
|
||||
KEY_FATFS_REQUIRED = "fatfs_required"
|
||||
KEY_MBEDTLS_SHA512_REQUIRED = "mbedtls_sha512_required"
|
||||
KEY_ADC_ONESHOT_IRAM_REQUIRED = "adc_oneshot_iram_required"
|
||||
KEY_LIBC_PICOLIBC_NEWLIB_COMPAT_REQUIRED = "libc_picolibc_newlib_compat_required"
|
||||
|
||||
@@ -1779,6 +1819,8 @@ def require_certificate_bundle() -> None:
|
||||
certificates (http_request, audio streaming) call this so the bundle is
|
||||
compiled and gen_crt_bundle runs only when something uses it.
|
||||
"""
|
||||
# esp_crt_bundle.c calls mbedtls_ssl_conf_*, so a bundle always needs TLS.
|
||||
request_tls()
|
||||
CORE.data[KEY_ESP32][KEY_CERT_BUNDLE] = True
|
||||
|
||||
|
||||
@@ -1804,33 +1846,29 @@ def require_usb_serial_jtag_secondary() -> None:
|
||||
CORE.data[KEY_ESP32][KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED] = True
|
||||
|
||||
|
||||
def require_mbedtls_peer_cert() -> None:
|
||||
"""Mark that mbedTLS peer certificate retention is required by a component.
|
||||
def require_mbedtls_ecp() -> None:
|
||||
"""Keep mbedTLS elliptic curve support (ECDH/ECDSA) without requesting TLS.
|
||||
|
||||
Call this from components that need access to the peer certificate after
|
||||
the TLS handshake is complete. This prevents CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
|
||||
from being disabled.
|
||||
Call this from components that sign or verify with ECDSA outside a TLS
|
||||
handshake (openthread's SRP host key). WiFi, Bluetooth and secure boot
|
||||
select it through Kconfig on their own.
|
||||
"""
|
||||
CORE.data[KEY_ESP32][KEY_MBEDTLS_PEER_CERT_REQUIRED] = True
|
||||
_mbedtls_sdkconfig().ecp_required = True
|
||||
|
||||
|
||||
def require_mbedtls_peer_cert() -> None:
|
||||
"""Keep the peer certificate after the TLS handshake (CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE)."""
|
||||
_mbedtls_sdkconfig().peer_cert_required = True
|
||||
|
||||
|
||||
def require_mbedtls_pkcs7() -> None:
|
||||
"""Mark that mbedTLS PKCS#7 support is required by a component.
|
||||
|
||||
Call this from components that need PKCS#7 certificate validation.
|
||||
This prevents CONFIG_MBEDTLS_PKCS7_C from being disabled.
|
||||
"""
|
||||
CORE.data[KEY_ESP32][KEY_MBEDTLS_PKCS7_REQUIRED] = True
|
||||
"""Keep mbedTLS PKCS#7 support (CONFIG_MBEDTLS_PKCS7_C)."""
|
||||
_mbedtls_sdkconfig().pkcs7_required = True
|
||||
|
||||
|
||||
def require_mbedtls_sha512() -> None:
|
||||
"""Mark that mbedTLS SHA-384/SHA-512 support is required by a component.
|
||||
|
||||
Call this from components that need to verify TLS certificates or signatures
|
||||
using SHA-384 or SHA-512 algorithms. This prevents CONFIG_MBEDTLS_SHA384_C
|
||||
and CONFIG_MBEDTLS_SHA512_C from being disabled.
|
||||
"""
|
||||
CORE.data[KEY_ESP32][KEY_MBEDTLS_SHA512_REQUIRED] = True
|
||||
"""Keep mbedTLS SHA-384/SHA-512 (CONFIG_MBEDTLS_SHA384_C / CONFIG_MBEDTLS_SHA512_C)."""
|
||||
_mbedtls_sdkconfig().sha512_required = True
|
||||
|
||||
|
||||
def idf_version() -> cv.Version:
|
||||
@@ -2295,6 +2333,62 @@ async def _reconcile_certificate_bundle_sdkconfig() -> None:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN", True)
|
||||
|
||||
|
||||
# User sdkconfig_options that mean "keep TLS on"; each becomes a request_tls().
|
||||
_MBEDTLS_TLS_ON_OPTIONS = (
|
||||
"CONFIG_MBEDTLS_TLS_ENABLED",
|
||||
"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT",
|
||||
"CONFIG_MBEDTLS_TLS_SERVER_ONLY",
|
||||
"CONFIG_MBEDTLS_TLS_CLIENT_ONLY",
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _reconcile_mbedtls_sdkconfig() -> None:
|
||||
"""Reconcile the mbedTLS sdkconfig flags after every request_tls() / require_mbedtls_*() call.
|
||||
|
||||
mbedtls cannot be excluded from an IDF build (bootloader_support needs its
|
||||
SHA-256), but with no TLS user the ssl_*.c sources and the TLS-only crypto
|
||||
compile to empty objects. User sdkconfig_options win.
|
||||
"""
|
||||
data = _mbedtls_sdkconfig()
|
||||
idf6 = idf_version() >= cv.Version(6, 0, 0)
|
||||
|
||||
if not CORE.using_arduino and not data.tls_required:
|
||||
# IDF 6 made CONFIG_MBEDTLS_TLS_ENABLED a normal bool; on IDF 5 it has
|
||||
# no prompt and is only reachable through the "None" TLS role choice.
|
||||
if idf6:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_TLS_ENABLED", False)
|
||||
else:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_TLS_DISABLED", True)
|
||||
# Enterprise WiFi selects TLS back on; wifi writes this itself, but
|
||||
# esp_wifi can also be in the build without a wifi: block (openthread).
|
||||
set_idf_sdkconfig_default("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", False)
|
||||
# WiFi, Bluetooth and secure boot re-select ECP through Kconfig.
|
||||
if not data.ecp_required:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_ECP_C", False)
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_PEM_WRITE_C", False)
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_X509_CRL_PARSE_C", False)
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_X509_CSR_PARSE_C", False)
|
||||
|
||||
# Keeping the peer certificate costs ~4KB heap per connection.
|
||||
if data.peer_cert_required:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", True)
|
||||
elif data.disable_peer_cert:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", False)
|
||||
|
||||
if data.pkcs7_required:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_PKCS7_C", True)
|
||||
elif data.disable_pkcs7:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_PKCS7_C", False)
|
||||
|
||||
# SHA-384 shares the SHA-512 compression function, so both go together.
|
||||
# Only IDF 6.0's PSA engine links a ~3KB software fallback for them; on
|
||||
# IDF 5 they are a single hardware-only option with no code size cost.
|
||||
if idf6 and not data.sha512_required:
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_SHA384_C", False)
|
||||
set_idf_sdkconfig_default("CONFIG_MBEDTLS_SHA512_C", False)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _reconcile_network_sdkconfig() -> None:
|
||||
"""Reconcile WiFi/Ethernet/Bluetooth/coexistence sdkconfig flags.
|
||||
@@ -2949,38 +3043,6 @@ async def to_code(config):
|
||||
if advanced[CONF_DISABLE_DEV_NULL_VFS]:
|
||||
add_idf_sdkconfig_option("CONFIG_VFS_INITIALIZE_DEV_NULL", False)
|
||||
|
||||
# Disable keeping peer certificate after TLS handshake
|
||||
# Saves ~4KB heap per connection, but prevents certificate inspection after handshake
|
||||
# Components that need it can call require_mbedtls_peer_cert()
|
||||
if CORE.data[KEY_ESP32].get(KEY_MBEDTLS_PEER_CERT_REQUIRED, False):
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", True)
|
||||
elif advanced[CONF_DISABLE_MBEDTLS_PEER_CERT]:
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", False)
|
||||
|
||||
# Disable PKCS#7 support in mbedTLS
|
||||
# Only needed for specific certificate validation scenarios
|
||||
# Components that need it can call require_mbedtls_pkcs7()
|
||||
if CORE.data[KEY_ESP32].get(KEY_MBEDTLS_PKCS7_REQUIRED, False):
|
||||
# Component called require_mbedtls_pkcs7() - enable regardless of user setting
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PKCS7_C", True)
|
||||
elif advanced[CONF_DISABLE_MBEDTLS_PKCS7]:
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PKCS7_C", False)
|
||||
|
||||
# Disable SHA-384 and SHA-512 in mbedTLS
|
||||
# ESPHome doesn't use either algorithm. SHA-384 shares the same
|
||||
# compression function as SHA-512 (mbedtls_internal_sha512_process),
|
||||
# so both must be disabled to eliminate the ~3KB software fallback
|
||||
# that IDF 6.0's PSA parallel engine always links in.
|
||||
# On IDF < 6.0 these are a single config and hardware-only (no
|
||||
# software fallback), so there was no code size cost to leaving
|
||||
# them enabled.
|
||||
# Components that need SHA-384/SHA-512 can call require_mbedtls_sha512()
|
||||
if idf_version() >= cv.Version(6, 0, 0) and not CORE.data[KEY_ESP32].get(
|
||||
KEY_MBEDTLS_SHA512_REQUIRED, False
|
||||
):
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA384_C", False)
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA512_C", False)
|
||||
|
||||
# FINAL priority: runs after every require_libc_picolibc_newlib_compat() call
|
||||
CORE.add_job(_set_libc_picolibc_newlib_compat)
|
||||
|
||||
@@ -2990,6 +3052,12 @@ async def to_code(config):
|
||||
# FINAL priority: runs after every require_certificate_bundle() call
|
||||
CORE.add_job(_reconcile_certificate_bundle_sdkconfig)
|
||||
|
||||
# FINAL priority: runs after every request_tls() / require_mbedtls_*() call
|
||||
mbedtls = _mbedtls_sdkconfig()
|
||||
mbedtls.disable_peer_cert = advanced[CONF_DISABLE_MBEDTLS_PEER_CERT]
|
||||
mbedtls.disable_pkcs7 = advanced[CONF_DISABLE_MBEDTLS_PKCS7]
|
||||
CORE.add_job(_reconcile_mbedtls_sdkconfig)
|
||||
|
||||
# FINAL: require_*() calls can come from to_code at or below this priority, so an
|
||||
# inline read would be iteration-order-dependent; reconcile once after every job ran.
|
||||
CORE.add_job(
|
||||
@@ -3021,6 +3089,12 @@ async def to_code(config):
|
||||
# so it still gets the CMN variant pinned.
|
||||
if conf[CONF_SDKCONFIG_OPTIONS].get("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE") == "y":
|
||||
require_certificate_bundle()
|
||||
# A TLS role or esp-tls option in sdkconfig_options means the user relies on TLS.
|
||||
elif any(
|
||||
name in _MBEDTLS_TLS_ON_OPTIONS or name.startswith("CONFIG_ESP_TLS_")
|
||||
for name in conf[CONF_SDKCONFIG_OPTIONS]
|
||||
):
|
||||
request_tls()
|
||||
|
||||
# Components from YAML are added in a separate coroutine with FINAL priority
|
||||
# Schedule it to run after all other components
|
||||
|
||||
@@ -30,6 +30,7 @@ KEY_EXTRA_BUILD_FILES = "extra_build_files"
|
||||
KEY_CERT_BUNDLE = "cert_bundle"
|
||||
KEY_FULL_CERT_BUNDLE = "full_cert_bundle"
|
||||
KEY_NETWORK_SDKCONFIG = "network_sdkconfig"
|
||||
KEY_MBEDTLS_SDKCONFIG = "mbedtls_sdkconfig"
|
||||
|
||||
VARIANT_ESP32 = "ESP32"
|
||||
VARIANT_ESP32C2 = "ESP32C2"
|
||||
|
||||
@@ -202,11 +202,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_watchdog_timeout(timeout_ms))
|
||||
|
||||
if CORE.is_esp32:
|
||||
# Re-enable ESP-IDF's HTTP client (excluded by default to save compile time).
|
||||
# esp-tls is re-enabled too because http_request includes <esp_tls.h>
|
||||
# directly and esp_http_client only pulls it in as a private dependency.
|
||||
esp32.include_builtin_idf_component("esp_http_client")
|
||||
esp32.include_builtin_idf_component("esp-tls")
|
||||
esp32.request_http_client()
|
||||
|
||||
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
|
||||
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.components.esp32 import (
|
||||
add_idf_sdkconfig_option,
|
||||
idf_version,
|
||||
include_builtin_idf_component,
|
||||
request_tls,
|
||||
)
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
@@ -364,8 +365,8 @@ async def to_code(config):
|
||||
add_idf_component(name="espressif/mqtt", ref="1.0.0")
|
||||
else:
|
||||
include_builtin_idf_component("mqtt")
|
||||
# mqtt_client.h drags in esp_tls types; esp-tls is excluded by default
|
||||
include_builtin_idf_component("esp-tls")
|
||||
# esp-mqtt links transport_ssl.c (esp_tls) even for plain MQTT
|
||||
request_tls()
|
||||
|
||||
cg.add_define("USE_MQTT")
|
||||
cg.add_global(mqtt_ns.using)
|
||||
|
||||
@@ -289,10 +289,7 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
if CORE.is_esp32:
|
||||
# Re-enable ESP-IDF's HTTP client (excluded by default to save compile time)
|
||||
# and esp-tls, whose sdkconfig options below need the component present
|
||||
esp32.include_builtin_idf_component("esp_http_client")
|
||||
esp32.include_builtin_idf_component("esp-tls")
|
||||
esp32.request_http_client()
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True)
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True
|
||||
|
||||
@@ -13,6 +13,7 @@ from esphome.components.esp32 import (
|
||||
get_esp32_variant,
|
||||
include_builtin_idf_component,
|
||||
only_on_variant,
|
||||
require_mbedtls_ecp,
|
||||
require_vfs_select,
|
||||
)
|
||||
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||
@@ -282,6 +283,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
# Re-enable openthread IDF component (excluded by default)
|
||||
if CORE.is_esp32:
|
||||
include_builtin_idf_component("openthread")
|
||||
# OPENTHREAD_CONFIG_ECDSA_ENABLE: the SRP client host key uses mbedtls_ecdsa_*
|
||||
require_mbedtls_ecp()
|
||||
|
||||
cg.add_define("USE_OPENTHREAD")
|
||||
if config.get(CONF_FORCE_DATASET):
|
||||
|
||||
@@ -17,9 +17,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
# Increase the maximum supported size of headers section in HTTP request packet to be processed by the server
|
||||
add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024)
|
||||
# Re-enable esp-tls (excluded by default to save compile time);
|
||||
# web_server_idf.cpp includes <esp_tls_crypto.h> for digest auth
|
||||
include_builtin_idf_component("esp-tls")
|
||||
# Re-enable ESP-IDF's HTTP server (excluded by default to save compile time).
|
||||
# Basic auth uses mbedtls_base64_encode directly, so no TLS stack is needed.
|
||||
include_builtin_idf_component("esp_http_server")
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esp_tls_crypto.h"
|
||||
#include <mbedtls/base64.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
@@ -544,14 +544,14 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw
|
||||
constexpr size_t max_digest_len = 350;
|
||||
char digest[max_digest_len];
|
||||
size_t out;
|
||||
esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest), max_digest_len, &out,
|
||||
reinterpret_cast<const uint8_t *>(user_info), user_info_len);
|
||||
mbedtls_base64_encode(reinterpret_cast<uint8_t *>(digest), max_digest_len, &out,
|
||||
reinterpret_cast<const uint8_t *>(user_info), user_info_len);
|
||||
|
||||
// Constant-time comparison to avoid timing side channels.
|
||||
// No early return on length mismatch — the length difference is folded
|
||||
// into the accumulator so any mismatch is rejected.
|
||||
const char *provided = auth_str + auth_prefix_len;
|
||||
size_t digest_len = out; // length from esp_crypto_base64_encode
|
||||
size_t digest_len = out;
|
||||
// Derive provided_len from the already-sized std::string rather than
|
||||
// rescanning with strlen (avoids attacker-controlled scan length).
|
||||
size_t provided_len = auth.value().size() - auth_prefix_len;
|
||||
|
||||
@@ -11,6 +11,7 @@ from esphome.components.esp32 import (
|
||||
const,
|
||||
get_esp32_variant,
|
||||
only_on_variant,
|
||||
request_tls,
|
||||
request_wifi,
|
||||
)
|
||||
from esphome.components.network import (
|
||||
@@ -656,6 +657,9 @@ async def to_code(config):
|
||||
# Disable Enterprise WiFi support if no EAP is configured
|
||||
if CORE.is_esp32:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", has_eap)
|
||||
# The supplicant's Kconfig select cannot override the IDF 5 TLS role choice
|
||||
if has_eap:
|
||||
request_tls()
|
||||
|
||||
# Only define USE_WIFI_MANUAL_IP if any AP uses manual IP
|
||||
if has_manual_ip:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32-c6-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
channel: 13
|
||||
network_name: OpenThread-8f28
|
||||
network_key: 0xdfd34f0f05cad978ec4e32b0413038ff
|
||||
pan_id: 0x8f28
|
||||
ext_pan_id: 0xd63e8e3e495ebbc3
|
||||
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
|
||||
mesh_local_prefix: fd53:145f:ed22:ad81::/64
|
||||
@@ -0,0 +1,9 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
sdkconfig_options:
|
||||
CONFIG_ESP_TLS_INSECURE: y
|
||||
@@ -0,0 +1,9 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
sdkconfig_options:
|
||||
CONFIG_MBEDTLS_TLS_CLIENT_ONLY: y
|
||||
@@ -0,0 +1,15 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
wifi:
|
||||
networks:
|
||||
- ssid: "test_ssid"
|
||||
eap:
|
||||
username: username
|
||||
password: password
|
||||
identity: identity
|
||||
@@ -16,15 +16,19 @@ from esphome.components.esp32 import (
|
||||
KEY_VFS_TERMIOS_REQUIRED,
|
||||
VARIANT_ESP32,
|
||||
VARIANTS,
|
||||
MbedtlsSdkconfigData,
|
||||
NetworkSdkconfigData,
|
||||
RawSdkconfigValue,
|
||||
_ota_downgrade_protection_errors,
|
||||
_reconcile_mbedtls_sdkconfig,
|
||||
_reconcile_network_sdkconfig,
|
||||
_reconcile_vfs_fatfs_sdkconfig,
|
||||
)
|
||||
from esphome.components.esp32.const import (
|
||||
KEY_ESP32,
|
||||
KEY_EXCLUDE_COMPONENTS,
|
||||
KEY_IDF_VERSION,
|
||||
KEY_MBEDTLS_SDKCONFIG,
|
||||
KEY_NETWORK_SDKCONFIG,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_VARIANT,
|
||||
@@ -260,8 +264,8 @@ def test_esp32_configuration_errors(
|
||||
("esp_driver_i2c", "esp_driver_ledc", "esp_driver_gptimer"),
|
||||
id="i2c_ledc_ac_dimmer",
|
||||
),
|
||||
# esp-tls has three owners; a per-owner config makes a dropped
|
||||
# re-include from any single one fail the test.
|
||||
# esp-tls comes back through request_tls(); a per-owner config makes
|
||||
# a dropped request from any single one fail the test.
|
||||
pytest.param(
|
||||
"exclusion_reincludes_http_request.yaml",
|
||||
("esp-tls", "esp_http_client"),
|
||||
@@ -275,8 +279,9 @@ def test_esp32_configuration_errors(
|
||||
id="mqtt",
|
||||
),
|
||||
pytest.param(
|
||||
# Basic auth uses mbedtls_base64_encode directly, so no esp-tls.
|
||||
"exclusion_reincludes_web_server.yaml",
|
||||
("esp-tls", "esp_http_server"),
|
||||
("esp_http_server",),
|
||||
id="web_server_idf",
|
||||
),
|
||||
pytest.param(
|
||||
@@ -403,6 +408,186 @@ def test_user_sdkconfig_certificate_bundle_wins(
|
||||
assert sdkconfig.get("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL") is False
|
||||
|
||||
|
||||
_TLS_OFF_CRYPTO = {
|
||||
"CONFIG_MBEDTLS_ECP_C": False,
|
||||
"CONFIG_MBEDTLS_PEM_WRITE_C": False,
|
||||
"CONFIG_MBEDTLS_X509_CRL_PARSE_C": False,
|
||||
"CONFIG_MBEDTLS_X509_CSR_PARSE_C": False,
|
||||
}
|
||||
_TLS_OFF_IDF5 = {
|
||||
"CONFIG_MBEDTLS_TLS_DISABLED": True,
|
||||
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT": False,
|
||||
**_TLS_OFF_CRYPTO,
|
||||
}
|
||||
_TLS_OFF_IDF6 = {
|
||||
"CONFIG_MBEDTLS_TLS_ENABLED": False,
|
||||
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT": False,
|
||||
**_TLS_OFF_CRYPTO,
|
||||
}
|
||||
_PEER_CERT_PKCS7_OFF = {
|
||||
"CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE": False,
|
||||
"CONFIG_MBEDTLS_PKCS7_C": False,
|
||||
}
|
||||
_IDF5 = cv.Version(5, 5, 5)
|
||||
_IDF6 = cv.Version(6, 0, 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("framework", "idf", "data", "preset", "expected"),
|
||||
[
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF5,
|
||||
MbedtlsSdkconfigData(),
|
||||
{},
|
||||
{**_TLS_OFF_IDF5, **_PEER_CERT_PKCS7_OFF},
|
||||
id="idf5_no_tls_user",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF6,
|
||||
MbedtlsSdkconfigData(),
|
||||
{},
|
||||
{
|
||||
**_TLS_OFF_IDF6,
|
||||
**_PEER_CERT_PKCS7_OFF,
|
||||
"CONFIG_MBEDTLS_SHA384_C": False,
|
||||
"CONFIG_MBEDTLS_SHA512_C": False,
|
||||
},
|
||||
id="idf6_drops_sha512",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF6,
|
||||
MbedtlsSdkconfigData(sha512_required=True),
|
||||
{},
|
||||
{**_TLS_OFF_IDF6, **_PEER_CERT_PKCS7_OFF},
|
||||
id="idf6_sha512_required",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF5,
|
||||
MbedtlsSdkconfigData(tls_required=True),
|
||||
{},
|
||||
_PEER_CERT_PKCS7_OFF,
|
||||
id="idf_tls_requested",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF5,
|
||||
MbedtlsSdkconfigData(ecp_required=True),
|
||||
{},
|
||||
{
|
||||
"CONFIG_MBEDTLS_TLS_DISABLED": True,
|
||||
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT": False,
|
||||
"CONFIG_MBEDTLS_PEM_WRITE_C": False,
|
||||
"CONFIG_MBEDTLS_X509_CRL_PARSE_C": False,
|
||||
"CONFIG_MBEDTLS_X509_CSR_PARSE_C": False,
|
||||
**_PEER_CERT_PKCS7_OFF,
|
||||
},
|
||||
id="idf_ecp_without_tls",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF5,
|
||||
MbedtlsSdkconfigData(),
|
||||
{"CONFIG_MBEDTLS_ECP_C": RawSdkconfigValue("y")},
|
||||
{
|
||||
**_TLS_OFF_IDF5,
|
||||
"CONFIG_MBEDTLS_ECP_C": RawSdkconfigValue("y"),
|
||||
**_PEER_CERT_PKCS7_OFF,
|
||||
},
|
||||
id="idf_user_ecp_wins",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF5,
|
||||
MbedtlsSdkconfigData(peer_cert_required=True, pkcs7_required=True),
|
||||
{},
|
||||
{
|
||||
**_TLS_OFF_IDF5,
|
||||
"CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE": True,
|
||||
"CONFIG_MBEDTLS_PKCS7_C": True,
|
||||
},
|
||||
id="idf_peer_cert_pkcs7_required",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
_IDF5,
|
||||
MbedtlsSdkconfigData(disable_peer_cert=False, disable_pkcs7=False),
|
||||
{},
|
||||
_TLS_OFF_IDF5,
|
||||
id="idf_advanced_disables_off",
|
||||
),
|
||||
pytest.param(
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
_IDF5,
|
||||
MbedtlsSdkconfigData(),
|
||||
{},
|
||||
_PEER_CERT_PKCS7_OFF,
|
||||
id="arduino_keeps_tls",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_reconcile_mbedtls_sdkconfig(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
framework: PlatformFramework,
|
||||
idf: cv.Version,
|
||||
data: MbedtlsSdkconfigData,
|
||||
preset: dict[str, Any],
|
||||
expected: dict[str, Any],
|
||||
) -> None:
|
||||
"""The FINAL-priority reconciler turns TLS off only when nothing requested it;
|
||||
user sdkconfig_options always win."""
|
||||
set_core_config(framework)
|
||||
CORE.data[KEY_ESP32] = {
|
||||
KEY_IDF_VERSION: idf,
|
||||
KEY_SDKCONFIG_OPTIONS: dict(preset),
|
||||
KEY_MBEDTLS_SDKCONFIG: data,
|
||||
}
|
||||
|
||||
asyncio.run(_reconcile_mbedtls_sdkconfig())
|
||||
|
||||
assert CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config_file", "tls_off", "ecp_off"),
|
||||
[
|
||||
pytest.param("network_ethernet_only.yaml", True, True, id="ethernet_api"),
|
||||
pytest.param(
|
||||
"exclusion_reincludes_web_server.yaml", True, True, id="web_server_idf"
|
||||
),
|
||||
# openthread's SRP host key needs ECDSA, so ECP stays while TLS is off
|
||||
pytest.param("tls_openthread_c6.yaml", True, False, id="openthread"),
|
||||
pytest.param(
|
||||
"exclusion_reincludes_http_request.yaml", False, False, id="http_request"
|
||||
),
|
||||
pytest.param("exclusion_reincludes_mqtt.yaml", False, False, id="mqtt"),
|
||||
pytest.param("exclusion_reincludes_nextion.yaml", False, False, id="nextion"),
|
||||
pytest.param("tls_wifi_eap.yaml", False, False, id="wifi_eap"),
|
||||
pytest.param(
|
||||
"certificate_bundle_sdkconfig.yaml", False, False, id="raw_bundle"
|
||||
),
|
||||
pytest.param("tls_sdkconfig_esp_tls.yaml", False, False, id="raw_esp_tls"),
|
||||
pytest.param("tls_sdkconfig_tls_role.yaml", False, False, id="raw_tls_role"),
|
||||
],
|
||||
)
|
||||
def test_tls_disabled_sdkconfig(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
config_file: str,
|
||||
tls_off: bool,
|
||||
ecp_off: bool,
|
||||
) -> None:
|
||||
"""TLS is compiled out unless a component or a raw sdkconfig option asks for it."""
|
||||
generate_main(component_config_path(config_file))
|
||||
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||
assert ("CONFIG_MBEDTLS_TLS_DISABLED" in sdkconfig) is tls_off
|
||||
assert ("CONFIG_MBEDTLS_ECP_C" in sdkconfig) is ecp_off
|
||||
assert ("esp-tls" in CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS]) is tls_off
|
||||
|
||||
|
||||
def test_execute_from_psram_s3_sdkconfig(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
|
||||
Reference in New Issue
Block a user