mirror of
https://github.com/esphome/esphome.git
synced 2025-11-10 20:05:48 +00:00
Compare commits
5 Commits
2025.9.2
...
release-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93e18e850e | ||
|
|
59c0ffb98b | ||
|
|
29658b79bc | ||
|
|
158a59aa83 | ||
|
|
c95180504a |
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.9.2
|
PROJECT_NUMBER = 2025.9.3
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ async def to_code(config):
|
|||||||
if key := encryption_config.get(CONF_KEY):
|
if key := encryption_config.get(CONF_KEY):
|
||||||
decoded = base64.b64decode(key)
|
decoded = base64.b64decode(key)
|
||||||
cg.add(var.set_noise_psk(list(decoded)))
|
cg.add(var.set_noise_psk(list(decoded)))
|
||||||
|
cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
|
||||||
else:
|
else:
|
||||||
# No key provided, but encryption desired
|
# No key provided, but encryption desired
|
||||||
# This will allow a plaintext client to provide a noise key,
|
# This will allow a plaintext client to provide a noise key,
|
||||||
|
|||||||
@@ -37,12 +37,14 @@ void APIServer::setup() {
|
|||||||
|
|
||||||
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
|
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
|
||||||
|
|
||||||
|
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||||
|
// Only load saved PSK if not set from YAML
|
||||||
SavedNoisePsk noise_pref_saved{};
|
SavedNoisePsk noise_pref_saved{};
|
||||||
if (this->noise_pref_.load(&noise_pref_saved)) {
|
if (this->noise_pref_.load(&noise_pref_saved)) {
|
||||||
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
||||||
|
|
||||||
this->set_noise_psk(noise_pref_saved.psk);
|
this->set_noise_psk(noise_pref_saved.psk);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Schedule reboot if no clients connect within timeout
|
// Schedule reboot if no clients connect within timeout
|
||||||
@@ -409,6 +411,12 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
|
|||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||||
|
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||||
|
// When PSK is set from YAML, this function should never be called
|
||||||
|
// but if it is, reject the change
|
||||||
|
ESP_LOGW(TAG, "Key set in YAML");
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
auto &old_psk = this->noise_ctx_->get_psk();
|
auto &old_psk = this->noise_ctx_->get_psk();
|
||||||
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
|
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
|
||||||
ESP_LOGW(TAG, "New PSK matches old");
|
ESP_LOGW(TAG, "New PSK matches old");
|
||||||
@@ -437,6 +445,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -288,11 +288,15 @@ void Sim800LComponent::parse_cmd_(std::string message) {
|
|||||||
if (item == 3) { // stat
|
if (item == 3) { // stat
|
||||||
uint8_t current_call_state = parse_number<uint8_t>(message.substr(start, end - start)).value_or(6);
|
uint8_t current_call_state = parse_number<uint8_t>(message.substr(start, end - start)).value_or(6);
|
||||||
if (current_call_state != this->call_state_) {
|
if (current_call_state != this->call_state_) {
|
||||||
|
if (current_call_state == 4) {
|
||||||
|
ESP_LOGV(TAG, "Premature call state '4'. Ignoring, waiting for RING");
|
||||||
|
} else {
|
||||||
|
this->call_state_ = current_call_state;
|
||||||
ESP_LOGD(TAG, "Call state is now: %d", current_call_state);
|
ESP_LOGD(TAG, "Call state is now: %d", current_call_state);
|
||||||
if (current_call_state == 0)
|
if (current_call_state == 0)
|
||||||
this->call_connected_callback_.call();
|
this->call_connected_callback_.call();
|
||||||
}
|
}
|
||||||
this->call_state_ = current_call_state;
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// item 4 = ""
|
// item 4 = ""
|
||||||
|
|||||||
@@ -242,7 +242,6 @@ void VoiceAssistant::loop() {
|
|||||||
msg.flags = flags;
|
msg.flags = flags;
|
||||||
msg.audio_settings = audio_settings;
|
msg.audio_settings = audio_settings;
|
||||||
msg.set_wake_word_phrase(StringRef(this->wake_word_));
|
msg.set_wake_word_phrase(StringRef(this->wake_word_));
|
||||||
this->wake_word_ = "";
|
|
||||||
|
|
||||||
// Reset media player state tracking
|
// Reset media player state tracking
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from esphome.enum import StrEnum
|
from esphome.enum import StrEnum
|
||||||
|
|
||||||
__version__ = "2025.9.2"
|
__version__ = "2025.9.3"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
esphome:
|
||||||
|
name: noise-key-test
|
||||||
|
|
||||||
|
host:
|
||||||
|
|
||||||
|
api:
|
||||||
|
encryption:
|
||||||
|
key: "zX9/JHxMKwpP0jUGsF0iESCm1wRvNgR6NkKVOhn7kSs="
|
||||||
|
|
||||||
|
logger:
|
||||||
51
tests/integration/test_noise_encryption_key_protection.py
Normal file
51
tests/integration/test_noise_encryption_key_protection.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"""Integration test for noise encryption key protection from YAML."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from aioesphomeapi import InvalidEncryptionKeyAPIError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_noise_encryption_key_protection(
|
||||||
|
yaml_config: str,
|
||||||
|
run_compiled: RunCompiledFunction,
|
||||||
|
api_client_connected: APIClientConnectedFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test that noise encryption key set in YAML cannot be changed via API."""
|
||||||
|
# The key that's set in the YAML fixture
|
||||||
|
noise_psk = "zX9/JHxMKwpP0jUGsF0iESCm1wRvNgR6NkKVOhn7kSs="
|
||||||
|
|
||||||
|
# Keep ESPHome process running throughout all tests
|
||||||
|
async with run_compiled(yaml_config):
|
||||||
|
# First connection - test key change attempt
|
||||||
|
async with api_client_connected(noise_psk=noise_psk) as client:
|
||||||
|
# Verify connection is established
|
||||||
|
device_info = await client.device_info()
|
||||||
|
assert device_info is not None
|
||||||
|
|
||||||
|
# Try to set a new encryption key via API
|
||||||
|
new_key = base64.b64encode(
|
||||||
|
b"x" * 32
|
||||||
|
) # Valid 32-byte key in base64 as bytes
|
||||||
|
|
||||||
|
# This should fail since key was set in YAML
|
||||||
|
success = await client.noise_encryption_set_key(new_key)
|
||||||
|
assert success is False
|
||||||
|
|
||||||
|
# Reconnect with the original key to verify it still works
|
||||||
|
async with api_client_connected(noise_psk=noise_psk) as client:
|
||||||
|
# Verify connection is still successful with original key
|
||||||
|
device_info = await client.device_info()
|
||||||
|
assert device_info is not None
|
||||||
|
assert device_info.name == "noise-key-test"
|
||||||
|
|
||||||
|
# Verify that connecting with a wrong key fails
|
||||||
|
wrong_key = base64.b64encode(b"y" * 32).decode() # Different key
|
||||||
|
with pytest.raises(InvalidEncryptionKeyAPIError):
|
||||||
|
async with api_client_connected(noise_psk=wrong_key) as client:
|
||||||
|
await client.device_info()
|
||||||
Reference in New Issue
Block a user