Fix Teslemetry energy-site setup crash on encrypted RSA key (#181348)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Brett Adams
2026-09-05 08:19:29 +02:00
committed by GitHub
co-authored by Copilot Autofix powered by AI
parent 0cb25fe472
commit 81e0a244f8
4 changed files with 77 additions and 4 deletions
@@ -333,9 +333,13 @@ async def _async_get_rsa_key_pem(hass: HomeAssistant) -> bytes:
pem: bytes | None = hass.data.get(RSA_PARENT_KEY)
if pem is None:
path = hass.config.path(POWERWALL_KEY_FILE)
await Teslemetry(
session=async_get_clientsession(hass), access_token=""
).get_rsa_private_key(path)
try:
await Teslemetry(
session=async_get_clientsession(hass), access_token=""
).get_rsa_private_key(path)
except TypeError as err:
# An encrypted PEM surfaces as TypeError from the cryptography loader.
raise ValueError("RSA private key file is encrypted") from err
pem = await hass.async_add_executor_job(Path(path).read_bytes)
hass.data[RSA_PARENT_KEY] = pem
return pem
@@ -273,7 +273,11 @@ class EnergySiteSubentryFlowHandler(ConfigSubentryFlow):
session=async_get_clientsession(self.hass), access_token=""
)
try:
await keyholder.get_rsa_private_key(path)
try:
await keyholder.get_rsa_private_key(path)
except TypeError as err:
# An encrypted PEM surfaces as TypeError from the cryptography loader.
raise ValueError("RSA private key file is encrypted") from err
self._key_pem = await self.hass.async_add_executor_job(
Path(path).read_bytes
)
@@ -1378,6 +1378,12 @@ async def test_pair_step_second_lookup_errors(
ValueError,
id="key_read_valueerror",
),
# An encrypted key PEM surfaces as TypeError from the cryptography loader.
pytest.param(
"homeassistant.components.teslemetry.config_flow.Teslemetry.get_rsa_private_key",
TypeError,
id="key_fetch_typeerror",
),
],
)
async def test_rsa_key_load_failure_aborts(
+59
View File
@@ -1418,6 +1418,65 @@ async def test_local_control_failure_falls_back_to_cloud(
)
async def test_local_control_encrypted_key_falls_back_to_cloud(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Fall back to cloud control when RSA key loading reports an encrypted PEM."""
entry = _entry_with_powerwall()
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.teslemetry.Teslemetry.get_rsa_private_key",
side_effect=TypeError(
"Password was not given but private key is encrypted"
),
),
patch("homeassistant.components.teslemetry.PLATFORMS", []),
caplog.at_level(logging.WARNING),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED
energysite = entry.runtime_data.energysites[0]
assert isinstance(energysite.api, EnergySite)
assert not isinstance(energysite.api, EnergySiteRouter)
assert energysite.can_local_control
assert "falling back to cloud control" in caplog.text
async def test_local_control_unexpected_typeerror_is_not_swallowed(
hass: HomeAssistant,
) -> None:
"""A TypeError outside the key load is a real bug and must not degrade silently.
``_LOCAL_CONTROL_ERRORS`` deliberately excludes TypeError: only the key
loader's encrypted-PEM TypeError is converted to ValueError. A TypeError
from anywhere else in the resolve path (here, client construction) must
fail setup rather than silently falling back to cloud control.
"""
entry = _entry_with_powerwall()
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.teslemetry._async_get_rsa_key_pem",
return_value=_TEST_RSA_KEY_PEM,
),
patch(
"homeassistant.components.teslemetry.PowerwallClient",
side_effect=TypeError("unexpected argument"),
),
patch("homeassistant.components.teslemetry.PLATFORMS", []),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_ERROR
async def test_get_rsa_key_pem_generates_and_caches(hass: HomeAssistant) -> None:
"""The RSA key is generated/read once, then served from the hass.data cache."""
with (