|
|
|
@@ -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}}
|
|
|
|
|