[ota] Offer encryption with the api key so enabling it works over OTA

This commit is contained in:
J. Nick Koston
2026-09-05 11:13:43 +02:00
parent d1829c495d
commit 029f6d4bc3
17 changed files with 425 additions and 32 deletions
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Callable
import logging
from typing import Any
@@ -11,6 +12,7 @@ from esphome import config_validation as cv
from esphome.components.esphome.ota import (
AUTO_LOAD,
FILTER_SOURCE_FILES,
_api_static_key,
_validate_no_password_with_encryption,
ota_esphome_final_validate,
)
@@ -386,6 +388,84 @@ def test_filter_source_files_excludes_noise_without_encryption() -> None:
CORE.config = old_config
def test_filter_source_files_keeps_noise_for_static_api_key() -> None:
"""A static api key makes the device offer encryption, so the transport
compiles even without an ota encryption block."""
old_config = CORE.config
ota = [_make_ota_config(port=3232)]
try:
CORE.config = {CONF_API: {CONF_ENCRYPTION: {CONF_KEY: API_KEY}}, CONF_OTA: ota}
assert FILTER_SOURCE_FILES() == []
# A runtime provisioned or all-zeros api key has nothing to offer
CORE.config = {CONF_API: {CONF_ENCRYPTION: {}}, CONF_OTA: ota}
assert FILTER_SOURCE_FILES() == ["ota_esphome_noise.cpp"]
CORE.config = {
CONF_API: {CONF_ENCRYPTION: {CONF_KEY: ZEROS_KEY}},
CONF_OTA: ota,
}
assert FILTER_SOURCE_FILES() == ["ota_esphome_noise.cpp"]
CORE.config = {CONF_API: {}, CONF_OTA: ota}
assert FILTER_SOURCE_FILES() == ["ota_esphome_noise.cpp"]
finally:
CORE.config = old_config
def test_api_static_key() -> None:
"""Only a real build-time api key can seed the encryption offer."""
assert _api_static_key({}) is None
assert _api_static_key({CONF_ENCRYPTION: {}}) is None
assert _api_static_key({CONF_ENCRYPTION: {CONF_KEY: ZEROS_KEY}}) is None
assert _api_static_key({CONF_ENCRYPTION: {CONF_KEY: API_KEY}}) == API_KEY
def _defines() -> set[str]:
return {define.name for define in CORE.defines}
def test_api_key_offers_encryption_without_requiring_it(
generate_main: Callable[[str], str],
) -> None:
"""An api key alone compiles the transport in and sets the psk, but the
device keeps accepting plaintext uploads."""
main_cpp = generate_main(
"tests/component_tests/ota/test_esphome_ota_api_key_offer.yaml"
)
assert "USE_OTA_ENCRYPTION" in _defines()
assert "USE_OTA_ENCRYPTION_REQUIRED" not in _defines()
assert "set_noise_psk(" in main_cpp
def test_api_key_offer_keeps_password(generate_main: Callable[[str], str]) -> None:
"""A password still guards plaintext uploads on an offering device."""
main_cpp = generate_main(
"tests/component_tests/ota/test_esphome_ota_api_key_offer_password.yaml"
)
assert {"USE_OTA_ENCRYPTION", "USE_OTA_PASSWORD"} <= _defines()
assert "USE_OTA_ENCRYPTION_REQUIRED" not in _defines()
assert "set_noise_psk(" in main_cpp
assert "set_auth_password(" in main_cpp
def test_encryption_block_requires_encryption(
generate_main: Callable[[str], str],
) -> None:
"""The ota encryption block is what makes the device refuse plaintext."""
main_cpp = generate_main(
"tests/component_tests/ota/test_esphome_ota_encryption_required.yaml"
)
assert {"USE_OTA_ENCRYPTION", "USE_OTA_ENCRYPTION_REQUIRED"} <= _defines()
assert "set_noise_psk(" in main_cpp
def test_runtime_api_key_offers_nothing(generate_main: Callable[[str], str]) -> None:
"""A key provisioned at runtime is unknown at build time, so no offer."""
main_cpp = generate_main(
"tests/component_tests/ota/test_esphome_ota_runtime_api_key.yaml"
)
assert "USE_OTA_ENCRYPTION" not in _defines()
assert "set_noise_psk(" not in main_cpp
def test_password_with_encryption_rejected() -> None:
"""The password and encryption options are mutually exclusive."""
config = {CONF_PASSWORD: "pw", CONF_ENCRYPTION: {CONF_KEY: API_KEY}}
@@ -0,0 +1,11 @@
esphome:
name: ota-offer
host:
api:
encryption:
key: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
ota:
- platform: esphome
@@ -0,0 +1,12 @@
esphome:
name: ota-offer-password
host:
api:
encryption:
key: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
ota:
- platform: esphome
password: "superlongpasswordthatnoonewillknow"
@@ -0,0 +1,12 @@
esphome:
name: ota-encryption-required
host:
api:
encryption:
key: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
ota:
- platform: esphome
encryption:
@@ -0,0 +1,10 @@
esphome:
name: ota-runtime-key
host:
api:
encryption:
ota:
- platform: esphome