mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 19:44:08 +00:00
[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:
co-authored by
pre-commit-ci-lite[bot]
parent
51dbd3a63f
commit
72413494b0
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user