mirror of
https://github.com/esphome/esphome.git
synced 2025-10-26 20:53:50 +00:00
Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
@@ -1572,7 +1572,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
|
|||||||
resp.success = false;
|
resp.success = false;
|
||||||
|
|
||||||
psk_t psk{};
|
psk_t psk{};
|
||||||
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
if (msg.key.empty()) {
|
||||||
|
if (this->parent_->clear_noise_psk(true)) {
|
||||||
|
resp.success = true;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Failed to clear encryption key");
|
||||||
|
}
|
||||||
|
} else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
||||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||||
|
|||||||
@@ -468,6 +468,31 @@ uint16_t APIServer::get_port() const { return this->port_; }
|
|||||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
|
bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg,
|
||||||
|
const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) {
|
||||||
|
if (!this->noise_pref_.save(&new_psk)) {
|
||||||
|
ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// ensure it's written immediately
|
||||||
|
if (!global_preferences->sync()) {
|
||||||
|
ESP_LOGW(TAG, "Failed to sync preferences");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg));
|
||||||
|
if (make_active) {
|
||||||
|
this->set_timeout(100, [this, active_psk]() {
|
||||||
|
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
||||||
|
this->set_noise_psk(active_psk);
|
||||||
|
for (auto &c : this->clients_) {
|
||||||
|
DisconnectRequest req;
|
||||||
|
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||||
// When PSK is set from YAML, this function should never be called
|
// When PSK is set from YAML, this function should never be called
|
||||||
@@ -482,27 +507,21 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SavedNoisePsk new_saved_psk{psk};
|
SavedNoisePsk new_saved_psk{psk};
|
||||||
if (!this->noise_pref_.save(&new_saved_psk)) {
|
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk,
|
||||||
ESP_LOGW(TAG, "Failed to save Noise PSK");
|
make_active);
|
||||||
return false;
|
#endif
|
||||||
}
|
}
|
||||||
// ensure it's written immediately
|
bool APIServer::clear_noise_psk(bool make_active) {
|
||||||
if (!global_preferences->sync()) {
|
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||||
ESP_LOGW(TAG, "Failed to sync preferences");
|
// When PSK is set from YAML, this function should never be called
|
||||||
return false;
|
// but if it is, reject the change
|
||||||
}
|
ESP_LOGW(TAG, "Key set in YAML");
|
||||||
ESP_LOGD(TAG, "Noise PSK saved");
|
return false;
|
||||||
if (make_active) {
|
#else
|
||||||
this->set_timeout(100, [this, psk]() {
|
SavedNoisePsk empty_psk{};
|
||||||
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
psk_t empty{};
|
||||||
this->set_noise_psk(psk);
|
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty,
|
||||||
for (auto &c : this->clients_) {
|
make_active);
|
||||||
DisconnectRequest req;
|
|
||||||
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool save_noise_psk(psk_t psk, bool make_active = true);
|
bool save_noise_psk(psk_t psk, bool make_active = true);
|
||||||
|
bool clear_noise_psk(bool make_active = true);
|
||||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
||||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||||
#endif // USE_API_NOISE
|
#endif // USE_API_NOISE
|
||||||
@@ -174,6 +175,10 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void schedule_reboot_timeout_();
|
void schedule_reboot_timeout_();
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
|
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||||
|
const psk_t &active_psk, bool make_active);
|
||||||
|
#endif // USE_API_NOISE
|
||||||
// Pointers and pointer-like types first (4 bytes each)
|
// Pointers and pointer-like types first (4 bytes each)
|
||||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||||
|
|||||||
@@ -243,8 +243,10 @@
|
|||||||
// Dummy firmware payload for shelly_dimmer
|
// Dummy firmware payload for shelly_dimmer
|
||||||
#define USE_SHD_FIRMWARE_MAJOR_VERSION 56
|
#define USE_SHD_FIRMWARE_MAJOR_VERSION 56
|
||||||
#define USE_SHD_FIRMWARE_MINOR_VERSION 5
|
#define USE_SHD_FIRMWARE_MINOR_VERSION 5
|
||||||
|
// clang-format off
|
||||||
#define USE_SHD_FIRMWARE_DATA \
|
#define USE_SHD_FIRMWARE_DATA \
|
||||||
{}
|
{}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
#define USE_WEBSERVER
|
#define USE_WEBSERVER
|
||||||
#define USE_WEBSERVER_AUTH
|
#define USE_WEBSERVER_AUTH
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
esphome:
|
||||||
|
name: noise-key-test
|
||||||
|
|
||||||
|
host:
|
||||||
|
|
||||||
|
api:
|
||||||
|
encryption:
|
||||||
|
key: "zX9/JHxMKwpP0jUGsF0iESCm1wRvNgR6NkKVOhn7kSs="
|
||||||
|
|
||||||
|
logger:
|
||||||
@@ -49,3 +49,42 @@ async def test_noise_encryption_key_protection(
|
|||||||
with pytest.raises(InvalidEncryptionKeyAPIError):
|
with pytest.raises(InvalidEncryptionKeyAPIError):
|
||||||
async with api_client_connected(noise_psk=wrong_key) as client:
|
async with api_client_connected(noise_psk=wrong_key) as client:
|
||||||
await client.device_info()
|
await client.device_info()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_noise_encryption_key_clear_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 = b"" # Empty key to attempt to clear
|
||||||
|
|
||||||
|
# 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