[esp32] Disable mbedTLS TLS and TLS-only crypto when no component needs them (#18877)

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
J. Nick Koston
2026-10-07 05:29:17 -10:00
committed by GitHub
co-authored by pre-commit-ci-lite[bot]
parent 51dbd3a63f
commit 72413494b0
16 changed files with 626 additions and 133 deletions
+2 -3
View File
@@ -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()
+228 -91
View File
@@ -1,6 +1,6 @@
from collections.abc import Callable, Iterable
import contextlib
from dataclasses import dataclass
from dataclasses import dataclass, field
import itertools
import logging
import os
@@ -76,6 +76,7 @@ from .const import (
KEY_FLASH_SIZE,
KEY_FULL_CERT_BUNDLE,
KEY_IDF_VERSION,
KEY_MBEDTLS_SDKCONFIG,
KEY_NETWORK_SDKCONFIG,
KEY_PATH,
KEY_REF,
@@ -237,7 +238,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
@@ -812,6 +813,97 @@ 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 the require_mbedtls_*() helpers (and request_tls(), which
sets tls_required and also un-excludes the esp-tls component) rather than
writing the CONFIG_MBEDTLS_* flags directly; _reconcile_mbedtls_sdkconfig()
decides the final values once every to_code has run.
"""
ecp_required: bool = False # ECDH/ECDSA without TLS (openthread SRP host key)
tls_required: bool = False # mbedTLS TLS role needed without the esp-tls wrapper
tls_server_required: bool = False # server-side TLS/DTLS handshake
tls_extras_required: set[str] = field(default_factory=set) # kept TLS extras
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_* options
disable_tls: bool = True
disable_tls_server: bool = True
disable_tls_extras: bool = True
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]
# IDF components that reference esp_tls symbols from their own code, so
# re-including any of them is an implicit TLS request.
_ESP_TLS_LINKING_COMPONENTS = (
"esp-tls",
"esp_http_client",
"esp_https_ota",
"esp_https_server",
"esp_local_ctrl",
"mqtt",
)
def _mbedtls_tls_required() -> bool:
"""TLS stays in the build: requested, or an esp_tls-linking component was re-included.
The exclusion-set signal keeps external components working whose only
obligation before request_tls() existed was include_builtin_idf_component()
of esp-tls or of a component that links it (esp_http_client, IDF mqtt).
"""
if _mbedtls_sdkconfig().tls_required:
return True
excluded = CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS]
return any(name not in excluded for name in _ESP_TLS_LINKING_COMPONENTS)
def _mbedtls_tls_compiled_out() -> bool:
"""True when this build removes the TLS stack from mbedTLS entirely."""
return (
not CORE.using_arduino
and _mbedtls_sdkconfig().disable_tls
and not _mbedtls_tls_required()
)
def require_mbedtls_tls() -> None:
"""Keep the mbedTLS TLS stack without compiling the esp-tls wrapper.
For code that talks to mbedTLS directly (wpa_supplicant's EAP client).
Components that use esp_tls call request_tls() instead.
"""
_mbedtls_sdkconfig().tls_required = True
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.
"""
require_mbedtls_tls()
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,
@@ -1847,6 +1939,7 @@ CONF_DISABLE_OCD_AWARE = "disable_ocd_aware"
CONF_DISABLE_USB_SERIAL_JTAG_SECONDARY = "disable_usb_serial_jtag_secondary"
CONF_DISABLE_DEV_NULL_VFS = "disable_dev_null_vfs"
CONF_DISABLE_MBEDTLS_PEER_CERT = "disable_mbedtls_peer_cert"
CONF_DISABLE_MBEDTLS_TLS = "disable_mbedtls_tls"
CONF_DISABLE_MBEDTLS_PKCS7 = "disable_mbedtls_pkcs7"
CONF_DISABLE_MBEDTLS_TLS_SERVER = "disable_mbedtls_tls_server"
CONF_DISABLE_MBEDTLS_TLS_EXTRAS = "disable_mbedtls_tls_extras"
@@ -1862,12 +1955,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_MBEDTLS_TLS_SERVER_REQUIRED = "mbedtls_tls_server_required"
KEY_MBEDTLS_TLS_EXTRAS_REQUIRED = "mbedtls_tls_extras_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"
@@ -1906,6 +1994,9 @@ 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 lives in the mbedtls component and calls
# mbedtls_ssl_conf_*, so a bundle needs the TLS role but not esp-tls.
require_mbedtls_tls()
CORE.data[KEY_ESP32][KEY_CERT_BUNDLE] = True
@@ -1931,33 +2022,37 @@ 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).
A user sdkconfig_options value takes precedence.
"""
_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). A user sdkconfig_options value takes precedence."""
_mbedtls_sdkconfig().pkcs7_required = True
def require_mbedtls_tls_server() -> None:
"""Mark that the mbedTLS server-side TLS/DTLS handshake is required.
"""Widen the TLS role to include the server-side handshake.
Call this from components that accept TLS connections (OpenThread's DTLS
commissioner does). This prevents CONFIG_MBEDTLS_TLS_CLIENT_ONLY from
being selected.
Only affects builds where TLS is compiled in; it prevents
CONFIG_MBEDTLS_TLS_CLIENT_ONLY from being selected. A component that
actually opens or accepts TLS/DTLS sessions must also call request_tls().
"""
CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_SERVER_REQUIRED] = True
_mbedtls_sdkconfig().tls_server_required = True
def require_mbedtls_tls_extras(options: Iterable[str] | None = None) -> None:
@@ -1970,18 +2065,14 @@ def require_mbedtls_tls_extras(options: Iterable[str] | None = None) -> None:
servers ESPHome cannot vet (wpa_supplicant's EAP client). A user-supplied
sdkconfig_options value is never overridden either.
"""
required = CORE.data[KEY_ESP32].setdefault(KEY_MBEDTLS_TLS_EXTRAS_REQUIRED, set())
required.update(MBEDTLS_TLS_EXTRA_OPTIONS if options is None else options)
_mbedtls_sdkconfig().tls_extras_required.update(
MBEDTLS_TLS_EXTRA_OPTIONS if options is None else options
)
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:
@@ -2135,6 +2226,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_DISABLE_DEV_NULL_VFS, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_PEER_CERT, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_PKCS7, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_TLS, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_TLS_SERVER, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_TLS_EXTRAS, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_REGI2C_IN_IRAM, default=True): cv.boolean,
@@ -2500,34 +2592,103 @@ MBEDTLS_TLS_ROLE_OPTIONS = (
)
@coroutine_with_priority(CoroPriority.FINAL)
async def _reconcile_mbedtls_tls_sdkconfig(
disable_tls_server: bool, disable_tls_extras: bool
) -> None:
"""Trim mbedTLS to what a TLS client needs unless a component asked otherwise.
# User sdkconfig_options that mean "keep TLS on" when set to y. The
# OpenThread entries compile its DTLS secure transport in, which links
# mbedtls_ssl_*.
_MBEDTLS_TLS_ON_OPTIONS = (
"CONFIG_MBEDTLS_TLS_ENABLED",
"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT",
"CONFIG_MBEDTLS_TLS_SERVER_ONLY",
"CONFIG_MBEDTLS_TLS_CLIENT_ONLY",
"CONFIG_OPENTHREAD_COMMISSIONER",
"CONFIG_OPENTHREAD_JOINER",
"CONFIG_OPENTHREAD_BORDER_AGENT_ENABLE",
# Border router defaults the border agent (and its DTLS) on.
"CONFIG_OPENTHREAD_BORDER_ROUTER",
# Enterprise WiFi selects TLS back on (see the TLS-off block).
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT",
)
# Any user option under these prefixes only makes sense with TLS compiled in.
_TLS_OPTION_PREFIXES = ("CONFIG_ESP_TLS_", "CONFIG_MBEDTLS_SSL_", "CONFIG_ESP_HTTPS_")
Runs at FINAL priority so every require_mbedtls_tls_server() and
require_mbedtls_tls_extras() call has happened. Only the server-side
handshake (~7 KB) is a separate option; nothing in ESPHome accepts TLS
connections, but OpenThread's DTLS commissioner does. A user-supplied
sdkconfig_options value always wins; for the TLS role choice, any member
the user set leaves the whole choice alone so the pair cannot conflict.
def _user_sdkconfig_wants_tls(options: dict[str, Any]) -> bool:
"""True when sdkconfig_options turn TLS on or tune something under it; an `n` is never a request."""
return any(
(name in _MBEDTLS_TLS_ON_OPTIONS and value == "y")
or (name == "CONFIG_MBEDTLS_TLS_DISABLED" and value == "n")
or (name.startswith(_TLS_OPTION_PREFIXES) and value != "n")
for name, value in options.items()
)
@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. When TLS stays in, it is trimmed to the client
role and the legacy handshake extras are dropped. User sdkconfig_options
win; a user-chosen TLS role leaves the whole choice alone.
"""
data = CORE.data[KEY_ESP32]
sdkconfig = data[KEY_SDKCONFIG_OPTIONS]
if (
disable_tls_server
and not data.get(KEY_MBEDTLS_TLS_SERVER_REQUIRED, False)
and not any(option in sdkconfig for option in MBEDTLS_TLS_ROLE_OPTIONS)
data = _mbedtls_sdkconfig()
idf6 = idf_version() >= cv.Version(6, 0, 0)
opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
if _mbedtls_tls_compiled_out():
# 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 (ESP_WIFI_MBEDTLS_CRYPTO) and Bluetooth deliberately stay on
# the select-wins path: an unconditional request from request_wifi()
# would defeat the ECP trim for users who disable that select.
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)
elif (
# TLS stays in: trim it to the client role unless a component accepts
# TLS connections or the user already chose a role.
data.disable_tls_server
and not data.tls_server_required
and not any(option in opts for option in MBEDTLS_TLS_ROLE_OPTIONS)
):
add_idf_sdkconfig_option("CONFIG_MBEDTLS_TLS_CLIENT_ONLY", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT", False)
if disable_tls_extras:
required = data.get(KEY_MBEDTLS_TLS_EXTRAS_REQUIRED, set())
# The extras run either way: CCM and deterministic ECDSA are plain
# crypto, not TLS-gated, so they matter even with TLS compiled out.
if data.disable_tls_extras:
for option in MBEDTLS_TLS_EXTRA_OPTIONS:
if option not in required:
if option not in data.tls_extras_required:
set_idf_sdkconfig_default(option, 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:
@@ -3150,6 +3311,10 @@ async def to_code(config):
for key, flag in SIGNING_SCHEMES.items():
add_idf_sdkconfig_option(flag, scheme == key)
if scheme in (SIGNING_SCHEME_ECDSA256, SIGNING_SCHEME_ECDSA_V1):
# SECURE_SIGNED_APPS selects ECP in Kconfig anyway; requesting it
# keeps the resolved sdkconfig consistent with what ESPHome wrote.
require_mbedtls_ecp()
if CONF_SIGNING_KEY in signed_ota:
# Private key mode — auto-sign binaries during build
@@ -3220,38 +3385,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)
@@ -3261,12 +3394,14 @@ 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 require_mbedtls_tls_*() call
CORE.add_job(
_reconcile_mbedtls_tls_sdkconfig,
advanced[CONF_DISABLE_MBEDTLS_TLS_SERVER],
advanced[CONF_DISABLE_MBEDTLS_TLS_EXTRAS],
)
# FINAL priority: runs after every request_tls() / require_mbedtls_*() call
mbedtls = _mbedtls_sdkconfig()
mbedtls.disable_tls = advanced[CONF_DISABLE_MBEDTLS_TLS]
mbedtls.disable_tls_server = advanced[CONF_DISABLE_MBEDTLS_TLS_SERVER]
mbedtls.disable_tls_extras = advanced[CONF_DISABLE_MBEDTLS_TLS_EXTRAS]
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.
@@ -3299,6 +3434,8 @@ 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()
if _user_sdkconfig_wants_tls(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
+1
View File
@@ -23,6 +23,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"
+1 -5
View File
@@ -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]))
+3 -2
View File
@@ -5,6 +5,7 @@ from esphome.components.esp32 import (
add_idf_component,
idf_version,
include_builtin_idf_component,
request_tls,
)
from esphome.config_helpers import (
filter_source_files_from_defines,
@@ -358,8 +359,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)
+1 -4
View File
@@ -271,10 +271,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
+6 -3
View File
@@ -13,6 +13,7 @@ from esphome.components.esp32 import (
get_esp32_variant,
include_builtin_idf_component,
only_on_variant,
require_mbedtls_ecp,
require_mbedtls_tls_extras,
require_mbedtls_tls_server,
require_vfs_select,
@@ -112,9 +113,9 @@ def set_sdkconfig_options(config: ConfigType) -> None:
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_ENABLED", True)
# OpenThread's DTLS commissioner is a TLS server, and its crypto platform
# uses AES-CCM and deterministic ECDSA directly. Keep the esp32 component
# from trimming them out of mbedTLS.
# Commissioner/joiner Kconfigs default off, so no mbedtls_ssl_* is linked;
# setting one under sdkconfig_options keeps TLS in the build automatically.
# The crypto platform uses AES-CCM and deterministic ECDSA directly.
require_mbedtls_tls_server()
require_mbedtls_tls_extras(
("CONFIG_MBEDTLS_CCM_C", "CONFIG_MBEDTLS_ECDSA_DETERMINISTIC")
@@ -293,6 +294,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
set_idf_sdkconfig_default("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")
@@ -12,7 +12,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>
@@ -554,14 +554,18 @@ 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);
// The buffer bound above makes failure unreachable; reject rather than
// compare against an unwritten digest if that ever changes.
if (mbedtls_base64_encode(reinterpret_cast<uint8_t *>(digest), max_digest_len, &out,
reinterpret_cast<const uint8_t *>(user_info), user_info_len) != 0) {
return false;
}
// 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;
+5 -4
View File
@@ -12,6 +12,7 @@ from esphome.components.esp32 import (
get_esp32_variant,
only_on_variant,
request_wifi,
require_mbedtls_tls,
require_mbedtls_tls_extras,
)
from esphome.components.network import (
@@ -683,10 +684,10 @@ async def to_code(config):
if CORE.is_esp32:
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", has_eap)
if has_eap:
# wpa_supplicant's EAP client negotiates with whatever the RADIUS
# server offers, and a failed handshake leaves the device off the
# network, so keep every mbedTLS client feature the esp32 platform
# would otherwise trim.
# The supplicant's Kconfig select cannot override the IDF 5 TLS
# role choice; the EAP client talks to mbedTLS directly (no
# esp-tls) and needs every trimmed extra.
require_mbedtls_tls()
require_mbedtls_tls_extras()
# Only define USE_WIFI_MANUAL_IP if any AP uses manual IP
@@ -10,6 +10,7 @@ from esphome.components.esp32 import (
add_idf_sdkconfig_option,
add_partition,
include_builtin_idf_component,
require_mbedtls_ecp,
require_mbedtls_tls_extras,
require_vfs_select,
)
@@ -384,6 +385,9 @@ async def esp32_to_code(config: ConfigType) -> "MockObj":
name="espressif/esp-zigbee-lib",
ref="2.0.4",
)
# The esp-zigbee-lib blobs reference mbedtls_ecp_* (Zigbee Direct, install
# code ECDH); keep ECP without relying on esp_wifi's Kconfig select.
require_mbedtls_ecp()
# Zigbee's crypto platform uses AES-CCM and deterministic ECDSA directly.
# Keep the esp32 component from trimming them out of mbedTLS.
@@ -12,6 +12,3 @@ wifi:
identity: "user@example.org"
username: "user"
password: "secret"
http_request:
verify_ssl: true
@@ -0,0 +1,9 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
advanced:
disable_mbedtls_tls: false
@@ -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_ENABLED: n
+338 -11
View File
@@ -11,25 +11,30 @@ from typing import Any
import pytest
from esphome.components.esp32 import (
_ESP_TLS_LINKING_COMPONENTS,
DEFAULT_EXCLUDED_IDF_COMPONENTS,
ESP32_FLASH_CHIPS,
KEY_FATFS_REQUIRED,
KEY_MBEDTLS_TLS_EXTRAS_REQUIRED,
KEY_MBEDTLS_TLS_SERVER_REQUIRED,
KEY_VFS_DIR_REQUIRED,
KEY_VFS_SELECT_REQUIRED,
KEY_VFS_TERMIOS_REQUIRED,
MBEDTLS_TLS_EXTRA_OPTIONS,
VARIANT_ESP32,
VARIANTS,
MbedtlsSdkconfigData,
NetworkSdkconfigData,
RawSdkconfigValue,
_ota_downgrade_protection_errors,
_reconcile_mbedtls_sdkconfig,
_reconcile_network_sdkconfig,
_reconcile_vfs_fatfs_sdkconfig,
_user_sdkconfig_wants_tls,
)
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,
@@ -322,8 +327,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"),
@@ -337,8 +342,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(
@@ -480,6 +486,303 @@ 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,
}
_TLS_EXTRAS_OFF = dict.fromkeys(MBEDTLS_TLS_EXTRA_OPTIONS, False)
_TLS_CLIENT_ONLY = {
"CONFIG_MBEDTLS_TLS_CLIENT_ONLY": True,
"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT": False,
}
_IDF5 = cv.Version(5, 5, 5)
_IDF6 = cv.Version(6, 0, 0)
@pytest.mark.parametrize(
("framework", "idf", "data", "preset", "expected", "excluded"),
[
pytest.param(
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(),
{},
{**_TLS_OFF_IDF5, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf5_no_tls_user",
),
pytest.param(
# An external component that only re-included esp-tls keeps TLS.
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(),
{},
{**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS) - {"esp-tls"},
id="idf_esp_tls_reincluded",
),
pytest.param(
# esp_http_client links esp_tls itself, so re-including it counts too.
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(),
{},
{**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS) - {"esp_http_client"},
id="idf_http_client_reincluded",
),
pytest.param(
PlatformFramework.ESP32_IDF,
_IDF6,
MbedtlsSdkconfigData(),
{},
{
**_TLS_OFF_IDF6,
**_TLS_EXTRAS_OFF,
**_PEER_CERT_PKCS7_OFF,
"CONFIG_MBEDTLS_SHA384_C": False,
"CONFIG_MBEDTLS_SHA512_C": False,
},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf6_drops_sha512",
),
pytest.param(
PlatformFramework.ESP32_IDF,
_IDF6,
MbedtlsSdkconfigData(sha512_required=True),
{},
{**_TLS_OFF_IDF6, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf6_sha512_required",
),
pytest.param(
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(ecp_required=True),
{},
{
**{
k: v
for k, v in _TLS_OFF_IDF5.items()
if k != "CONFIG_MBEDTLS_ECP_C"
},
**_TLS_EXTRAS_OFF,
**_PEER_CERT_PKCS7_OFF,
},
set(_ESP_TLS_LINKING_COMPONENTS),
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"),
**_TLS_EXTRAS_OFF,
**_PEER_CERT_PKCS7_OFF,
},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf_user_ecp_wins",
),
pytest.param(
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(peer_cert_required=True, pkcs7_required=True),
{},
{
**_TLS_OFF_IDF5,
**_TLS_EXTRAS_OFF,
"CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE": True,
"CONFIG_MBEDTLS_PKCS7_C": True,
},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf_peer_cert_pkcs7_required",
),
pytest.param(
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(disable_peer_cert=False, disable_pkcs7=False),
{},
{**_TLS_OFF_IDF5, **_TLS_EXTRAS_OFF},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf_advanced_disables_off",
),
pytest.param(
# advanced: disable_mbedtls_tls: false keeps TLS with no requester.
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(disable_tls=False),
{},
{**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf_disable_tls_opt_out",
),
pytest.param(
# require_mbedtls_tls() keeps TLS with every wrapper still excluded.
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(tls_required=True),
{},
{**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS),
id="idf_require_mbedtls_tls",
),
pytest.param(
# TLS kept: a required server role blocks the client-only trim.
PlatformFramework.ESP32_IDF,
_IDF5,
MbedtlsSdkconfigData(tls_server_required=True),
{},
{**_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS) - {"esp-tls"},
id="idf_tls_server_required",
),
pytest.param(
PlatformFramework.ESP32_ARDUINO,
_IDF5,
MbedtlsSdkconfigData(),
{},
{**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF},
set(_ESP_TLS_LINKING_COMPONENTS),
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],
excluded: set[str],
) -> 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,
KEY_EXCLUDE_COMPONENTS: excluded,
}
asyncio.run(_reconcile_mbedtls_sdkconfig())
assert CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] == expected
def test_esp_tls_linking_components_are_excluded_by_default() -> None:
"""The fallback scan is only a real signal while every name is excluded by default."""
assert set(_ESP_TLS_LINKING_COMPONENTS) <= set(DEFAULT_EXCLUDED_IDF_COMPONENTS)
@pytest.mark.parametrize(
("options", "wants_tls"),
[
pytest.param({}, False, id="empty"),
pytest.param({"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT": "y"}, True, id="role_y"),
pytest.param({"CONFIG_MBEDTLS_TLS_ENABLED": "n"}, False, id="enabled_n"),
pytest.param({"CONFIG_MBEDTLS_TLS_DISABLED": "n"}, True, id="disabled_n"),
pytest.param({"CONFIG_ESP_TLS_INSECURE": "y"}, True, id="esp_tls_prefix"),
pytest.param(
{"CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE": "n"},
False,
id="prefix_n_is_not_a_request",
),
pytest.param({"CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP": "y"}, True, id="https_prefix"),
pytest.param({"CONFIG_OPENTHREAD_COMMISSIONER": "y"}, True, id="ot_dtls_y"),
pytest.param({"CONFIG_OPENTHREAD_JOINER": "n"}, False, id="ot_dtls_n"),
pytest.param({"CONFIG_OPENTHREAD_BORDER_ROUTER": "y"}, True, id="ot_br_y"),
pytest.param(
{"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT": "y"}, True, id="wifi_enterprise_y"
),
pytest.param({"CONFIG_LWIP_IPV6": "y"}, False, id="unrelated"),
],
)
def test_user_sdkconfig_wants_tls(options: dict[str, Any], wants_tls: bool) -> None:
"""The sdkconfig escape hatch reads values, never bare key presence."""
assert _user_sdkconfig_wants_tls(options) is wants_tls
@pytest.mark.parametrize(
("config_file", "tls_off", "ecp_off", "esp_tls_excluded"),
[
pytest.param("network_ethernet_only.yaml", True, True, True, id="ethernet_api"),
pytest.param(
"exclusion_reincludes_web_server.yaml",
True,
True,
True,
id="web_server_idf",
),
pytest.param(
"exclusion_reincludes_http_request.yaml",
False,
False,
False,
id="http_request",
),
pytest.param("exclusion_reincludes_mqtt.yaml", False, False, False, id="mqtt"),
pytest.param(
"exclusion_reincludes_nextion.yaml", False, False, False, id="nextion"
),
pytest.param("mbedtls_tls_wifi_eap.yaml", False, False, True, id="wifi_eap"),
# zigbee requests ECP for the esp-zigbee-lib blobs, without TLS.
pytest.param("tls_zigbee_c6.yaml", True, False, True, id="zigbee"),
# A raw bundle keeps the TLS role but no longer compiles esp-tls.
pytest.param(
"certificate_bundle_sdkconfig.yaml", False, False, True, id="raw_bundle"
),
pytest.param(
"tls_sdkconfig_esp_tls.yaml", False, False, False, id="raw_esp_tls"
),
# A role option set to n is not a request.
pytest.param(
"tls_sdkconfig_tls_enabled_n.yaml", True, True, True, id="raw_tls_enabled_n"
),
# ECDSA signed OTA requests ECP itself (SECURE_SIGNED_APPS selects it too).
pytest.param(
"signed_ota_ecdsa256_c6.yaml", True, False, True, id="signed_ota_ecdsa"
),
],
)
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,
esp_tls_excluded: 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 (sdkconfig.get("CONFIG_MBEDTLS_TLS_DISABLED") is True) is tls_off
assert sdkconfig.get("CONFIG_MBEDTLS_ECP_C") is (False if ecp_off else None)
assert (
"esp-tls" in CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS]
) is esp_tls_excluded
def test_execute_from_psram_s3_sdkconfig(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
@@ -1496,9 +1799,14 @@ def test_mbedtls_tls_openthread_keeps_only_what_it_uses(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""The OpenThread config keeps the DTLS server, CCM and deterministic ECDSA; the rest is trimmed."""
"""Nothing in the OpenThread config links TLS, so the stack is compiled out
and no TLS role is written; the extras trim still runs because CCM and
deterministic ECDSA are plain crypto, and OpenThread keeps those two."""
generate_main(component_config_path("mbedtls_tls_openthread.yaml"))
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
assert sdkconfig.get("CONFIG_MBEDTLS_TLS_DISABLED") is True
# require_mbedtls_ecp() keeps ECP for the SRP host key while TLS is off
assert "CONFIG_MBEDTLS_ECP_C" not in sdkconfig
assert tuple(sdkconfig.get(name) for name in _TLS_SERVER_OPTIONS) == (None, None)
for name in MBEDTLS_TLS_EXTRA_OPTIONS:
assert sdkconfig.get(name) is (None if name in _CCM_ECDSA_EXTRAS else False)
@@ -1508,14 +1816,31 @@ def test_mbedtls_tls_zigbee_keeps_only_what_it_uses(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""The Zigbee config keeps CCM and deterministic ECDSA; the rest is trimmed."""
"""Nothing in the Zigbee config links TLS, so the stack is compiled out and
no role is written; the extras trim still runs and Zigbee keeps CCM and
deterministic ECDSA."""
generate_main(component_config_path("tls_zigbee_c6.yaml"))
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
assert tuple(sdkconfig.get(name) for name in _TLS_SERVER_OPTIONS) == (True, False)
assert sdkconfig.get("CONFIG_MBEDTLS_TLS_DISABLED") is True
assert tuple(sdkconfig.get(name) for name in _TLS_SERVER_OPTIONS) == (None, None)
for name in MBEDTLS_TLS_EXTRA_OPTIONS:
assert sdkconfig.get(name) is (None if name in _CCM_ECDSA_EXTRAS else False)
def test_mbedtls_tls_opt_out_keeps_stack_and_trims_role(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""disable_mbedtls_tls: false keeps TLS with no requester; the client-only
and extras trims then still apply."""
generate_main(component_config_path("tls_keep_opt_out.yaml"))
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
assert "CONFIG_MBEDTLS_TLS_DISABLED" not in sdkconfig
assert "CONFIG_MBEDTLS_ECP_C" not in sdkconfig
assert sdkconfig.get("CONFIG_MBEDTLS_TLS_CLIENT_ONLY") is True
assert sdkconfig.get("CONFIG_MBEDTLS_SSL_RENEGOTIATION") is False
def test_mbedtls_tls_user_sdkconfig_wins(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
@@ -1541,8 +1866,9 @@ def test_mbedtls_tls_openthread_requires_server_and_extras(
) -> None:
"""The OpenThread hooks mark the DTLS server and CCM/deterministic ECDSA as required."""
generate_main(component_config_path("mbedtls_tls_openthread.yaml"))
assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_SERVER_REQUIRED] is True
assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_EXTRAS_REQUIRED] == _CCM_ECDSA_EXTRAS
mbedtls = CORE.data[KEY_ESP32][KEY_MBEDTLS_SDKCONFIG]
assert mbedtls.tls_server_required is True
assert mbedtls.tls_extras_required == _CCM_ECDSA_EXTRAS
def test_mbedtls_tls_zigbee_requires_extras(
@@ -1551,7 +1877,8 @@ def test_mbedtls_tls_zigbee_requires_extras(
) -> None:
"""The Zigbee hooks mark the CCM/deterministic ECDSA as required."""
generate_main(component_config_path("tls_zigbee_c6.yaml"))
assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_EXTRAS_REQUIRED] == _CCM_ECDSA_EXTRAS
mbedtls = CORE.data[KEY_ESP32][KEY_MBEDTLS_SDKCONFIG]
assert mbedtls.tls_extras_required == _CCM_ECDSA_EXTRAS
_VASPRINTF_STUB_FLAGS = {"-Wl,--wrap=vasprintf", "-Wl,--undefined=__wrap_vasprintf"}