diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2eb8c21c73..ef6537dce2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -2130,7 +2130,7 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio } #endif - psk_t psk{}; + noise::psk_t psk{}; if (msg.key_len == 0) { if (this->parent_->clear_noise_psk(true)) { resp.success = true; @@ -2139,7 +2139,7 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio } } else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); - } else if (APINoiseContext::is_all_zeros(psk)) { + } else if (noise::NoiseContext::is_all_zeros(psk)) { // Accepting the reserved provisioning PSK would report success without // enabling encryption (or silently clear an existing key) ESP_LOGW(TAG, "Rejecting all-zero encryption key"); diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 54a5fdca90..98071b2706 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -5,7 +5,6 @@ #include "esphome/components/noise/noise.h" #include "esphome/core/application.h" #include "esphome/core/entity_base.h" -#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "proto.h" @@ -318,15 +317,15 @@ APIError APINoiseFrameHelper::state_action_server_hello_() { return APIError::OK; } APIError APINoiseFrameHelper::state_action_handshake_() { - int action = noise_handshakestate_get_action(this->handshake_); - if (action == NOISE_ACTION_READ_MESSAGE) { + noise::NoiseResponderHandshake::Action action = this->handshake_.action(); + if (action == noise::NoiseResponderHandshake::Action::ACTION_READ) { return this->state_action_handshake_read_(); - } else if (action == NOISE_ACTION_WRITE_MESSAGE) { + } else if (action == noise::NoiseResponderHandshake::Action::ACTION_WRITE) { return this->state_action_handshake_write_(); } // bad state for action this->state_ = State::FAILED; - HELPER_LOG("Bad action for handshake: %d", action); + HELPER_LOG("Bad action for handshake: %d", (int) action); return APIError::HANDSHAKESTATE_BAD_STATE; } APIError APINoiseFrameHelper::state_action_handshake_read_() { @@ -344,14 +343,10 @@ APIError APINoiseFrameHelper::state_action_handshake_read_() { return APIError::BAD_HANDSHAKE_ERROR_BYTE; } - NoiseBuffer mbuf; - noise_buffer_init(mbuf); - noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1); - int err = noise_handshakestate_read_message(this->handshake_, &mbuf, nullptr); + int err = this->handshake_.read_message(this->rx_buf_.data() + 1, this->rx_buf_.size() - 1); if (err != 0) { // Special handling for MAC failure - this->send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure") - : LOG_STR("Handshake error")); + this->send_explicit_handshake_reject_(noise::reject_reason_for(err)); return this->handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"), APIError::HANDSHAKESTATE_READ_FAILED); } @@ -360,18 +355,16 @@ APIError APINoiseFrameHelper::state_action_handshake_read_() { } APIError APINoiseFrameHelper::state_action_handshake_write_() { uint8_t buffer[65]; - NoiseBuffer mbuf; - noise_buffer_init(mbuf); - noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); + size_t msg_len = 0; - int err = noise_handshakestate_write_message(this->handshake_, &mbuf, nullptr); + int err = this->handshake_.write_message(buffer + 1, sizeof(buffer) - 1, msg_len); APIError aerr = this->handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"), APIError::HANDSHAKESTATE_WRITE_FAILED); if (aerr != APIError::OK) return aerr; - buffer[0] = 0x00; // success + buffer[0] = noise::HANDSHAKE_STATUS_OK; - aerr = this->write_frame_(buffer, mbuf.size + 1); + aerr = this->write_frame_(buffer, msg_len + 1); if (aerr != APIError::OK) return aerr; return this->check_handshake_finished_(); @@ -379,6 +372,8 @@ APIError APINoiseFrameHelper::state_action_handshake_write_() { void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) { // Max reject message: "Bad handshake packet len" (24) + 1 (failure byte) = 25 bytes uint8_t data[32]; + static_assert(sizeof(data) >= noise::MAC_FAILURE_PAYLOAD_SIZE, + "reject buffer must fit the MAC failure wire contract"); size_t data_size = noise::format_reject_payload(data, sizeof(data), reason); // temporarily remove failed state @@ -515,19 +510,19 @@ APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, s } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { - uint8_t header[3]; + uint8_t header[noise::FRAME_HEADER_SIZE]; noise::write_frame_header(header, len); if (len == 0) { - return this->write_raw_buf_(header, 3); + return this->write_raw_buf_(header, noise::FRAME_HEADER_SIZE); } struct iovec iov[2]; iov[0].iov_base = header; - iov[0].iov_len = 3; + iov[0].iov_len = noise::FRAME_HEADER_SIZE; iov[1].iov_base = const_cast(data); iov[1].iov_len = len; - return this->write_raw_iov_(iov, 2, 3 + len); + return this->write_raw_iov_(iov, 2, noise::FRAME_HEADER_SIZE + len); } /** Initiate the data structures for the handshake. @@ -535,45 +530,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { * @return 0 on success, -1 on error (check errno) */ APIError APINoiseFrameHelper::init_handshake_() { - int err; - // Noise_NNpsk0_25519_ChaChaPoly_SHA256, built on the stack: - // noise_handshakestate_new_by_id copies it, so a member would waste - // 104 bytes per connection, and a static const would sit in RAM on - // ESP8266 (.rodata is DRAM there). - const NoiseProtocolId nid = { - .prefix_id = NOISE_PREFIX_STANDARD, - .pattern_id = NOISE_PATTERN_NN, - .modifier_ids = {NOISE_MODIFIER_PSK0}, - .dh_id = NOISE_DH_CURVE25519, - .cipher_id = NOISE_CIPHER_CHACHAPOLY, - .hash_id = NOISE_HASH_SHA256, - .hybrid_id = NOISE_DH_NONE, - }; - - err = noise_handshakestate_new_by_id(&handshake_, &nid, NOISE_ROLE_RESPONDER); - APIError aerr = - handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED); + int err = this->handshake_.init(this->ctx_.get_psk(), prologue_.data(), prologue_.size()); + APIError aerr = handle_noise_error_(err, LOG_STR("noise_handshake_init"), APIError::HANDSHAKESTATE_SETUP_FAILED); if (aerr != APIError::OK) return aerr; - - const auto &psk = this->ctx_.get_psk(); - err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size()); - aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"), - APIError::HANDSHAKESTATE_SETUP_FAILED); - if (aerr != APIError::OK) - return aerr; - - err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size()); - aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED); - if (aerr != APIError::OK) - return aerr; - // set_prologue copies it into handshakestate, so we can get rid of it now + // init copies the prologue into the handshakestate, so we can get rid of it now prologue_.release(); - - err = noise_handshakestate_start(handshake_); - aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED); - if (aerr != APIError::OK) - return aerr; return APIError::OK; } @@ -582,15 +544,17 @@ APIError APINoiseFrameHelper::check_handshake_finished_() { assert(state_ == State::HANDSHAKE); #endif - int action = noise_handshakestate_get_action(handshake_); - if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE) + noise::NoiseResponderHandshake::Action action = this->handshake_.action(); + if (action == noise::NoiseResponderHandshake::Action::ACTION_READ || + action == noise::NoiseResponderHandshake::Action::ACTION_WRITE) return APIError::OK; - if (action != NOISE_ACTION_SPLIT) { + if (action != noise::NoiseResponderHandshake::Action::ACTION_SPLIT) { state_ = State::FAILED; - HELPER_LOG("Bad action for handshake: %d", action); + HELPER_LOG("Bad action for handshake: %d", (int) action); return APIError::HANDSHAKESTATE_BAD_STATE; } - int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_); + // split() also frees the handshake state + int err = this->handshake_.split(send_cipher_, recv_cipher_); APIError aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED); if (aerr != APIError::OK) @@ -599,17 +563,11 @@ APIError APINoiseFrameHelper::check_handshake_finished_() { this->frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_); HELPER_LOG("Handshake complete!"); - noise_handshakestate_free(handshake_); - handshake_ = nullptr; state_ = State::DATA; return APIError::OK; } APINoiseFrameHelper::~APINoiseFrameHelper() { - if (handshake_ != nullptr) { - noise_handshakestate_free(handshake_); - handshake_ = nullptr; - } if (send_cipher_ != nullptr) { noise_cipherstate_free(send_cipher_); send_cipher_ = nullptr; diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 958383b962..f73dcfbb94 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -3,7 +3,7 @@ #ifdef USE_API #ifdef USE_API_NOISE #include "noise/protocol.h" -#include "api_noise_context.h" +#include "esphome/components/noise/noise_handshake.h" namespace esphome::api { @@ -16,7 +16,7 @@ class APINoiseFrameHelper final : public APIFrameHelper { // Pos 7+: actual payload data static constexpr uint8_t HEADER_PADDING = 1 + 2 + 2 + 2; // indicator + size + type + data_len - APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx) + APINoiseFrameHelper(std::unique_ptr socket, noise::NoiseContext &ctx) : APIFrameHelper(std::move(socket)), ctx_(ctx) { frame_header_padding_ = HEADER_PADDING; } @@ -52,13 +52,13 @@ class APINoiseFrameHelper final : public APIFrameHelper { APIError handle_handshake_frame_error_(APIError aerr); APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err); - // Pointers first (4 bytes each) - NoiseHandshakeState *handshake_{nullptr}; + // Pointers first (4 bytes each; the handshake wrapper holds one pointer) + noise::NoiseResponderHandshake handshake_; NoiseCipherState *send_cipher_{nullptr}; NoiseCipherState *recv_cipher_{nullptr}; // Reference to noise context (4 bytes on 32-bit) - APINoiseContext &ctx_; + noise::NoiseContext &ctx_; // Buffer for noise handshake prologue (released after handshake) APIBuffer prologue_; diff --git a/esphome/components/api/api_noise_context.h b/esphome/components/api/api_noise_context.h deleted file mode 100644 index b3b00bf00c..0000000000 --- a/esphome/components/api/api_noise_context.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include "esphome/core/defines.h" - -#ifdef USE_API_NOISE -#include "esphome/components/noise/noise.h" - -namespace esphome::api { - -// Kept as aliases for external components that use the api names -using psk_t = noise::psk_t; -using APINoiseContext = noise::NoiseContext; - -} // namespace esphome::api -#endif // USE_API_NOISE diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index ef5b43d7b1..45f90a348d 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -598,7 +598,7 @@ bool APIServer::load_and_apply_noise_psk_() { return true; } -bool APIServer::save_noise_psk(psk_t psk, bool make_active) { +bool APIServer::save_noise_psk(noise::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 diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 248b83a0ff..e5ba3d20df 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -5,7 +5,10 @@ #include "api_buffer.h" // Must precede clients_ so APIConnection is complete for default_delete (libc++). #include "api_connection.h" -#include "api_noise_context.h" +#ifdef USE_API_NOISE +// Only present in the build when the noise component is loaded +#include "esphome/components/noise/noise.h" +#endif #include "api_pb2.h" #include "api_pb2_service.h" #include "esphome/components/socket/socket.h" @@ -37,7 +40,7 @@ class UserServiceDescriptor; #ifdef USE_API_NOISE struct SavedNoisePsk { - psk_t psk; + noise::psk_t psk; } PACKED; // NOLINT #endif @@ -73,10 +76,10 @@ class APIServer final : public Component, APIBuffer &get_shared_buffer_ref() { return shared_write_buffer_; } #ifdef USE_API_NOISE - bool save_noise_psk(psk_t psk, bool make_active = true); + bool save_noise_psk(noise::psk_t psk, bool make_active = true); bool clear_noise_psk(bool make_active = true); - void set_noise_psk(psk_t psk) { this->noise_ctx_.set_psk(psk); } - APINoiseContext &get_noise_ctx() { return this->noise_ctx_; } + void set_noise_psk(noise::psk_t psk) { this->noise_ctx_.set_psk(psk); } + noise::NoiseContext &get_noise_ctx() { return this->noise_ctx_; } #endif // USE_API_NOISE void handle_disconnect(APIConnection *conn); @@ -354,7 +357,7 @@ class APIServer final : public Component, #endif #ifdef USE_API_NOISE - APINoiseContext noise_ctx_; + noise::NoiseContext noise_ctx_; ESPPreferenceObject noise_pref_; #endif // USE_API_NOISE }; diff --git a/esphome/components/noise/__init__.py b/esphome/components/noise/__init__.py index 697931591a..fba7e78301 100644 --- a/esphome/components/noise/__init__.py +++ b/esphome/components/noise/__init__.py @@ -4,7 +4,6 @@ import binascii import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_KEY -from esphome.core import CORE from esphome.types import ConfigType CODEOWNERS = ["@bdraco"] @@ -67,11 +66,3 @@ async def to_code(config: ConfigType) -> None: # Enable optimized memzero/memcmp in libsodium instead of volatile byte loops cg.add_build_flag("-DHAVE_WEAK_SYMBOLS=1") cg.add_build_flag("-DHAVE_INLINE_ASM=1") - # noise-c pulls noise_rand_bytes (our HWRNG binding, defined in noise.cpp) - # from the static archive. A consumer such as api may reference no other - # symbol from noise.cpp at normal log levels, so on the ESP-IDF link (which - # resolves archives in a group) the member is dropped and noise-c fails to - # link. Force the linker to keep it. The host toolchain links it without - # help, and its ld syntax differs (leading underscore), so skip it there. - if not CORE.is_host: - cg.add_build_flag("-Wl,-u,noise_rand_bytes") diff --git a/esphome/components/noise/noise.cpp b/esphome/components/noise/noise.cpp index 8597122af8..95fab322db 100644 --- a/esphome/components/noise/noise.cpp +++ b/esphome/components/noise/noise.cpp @@ -1,7 +1,5 @@ #include "noise.h" #ifdef USE_NOISE -#include "esphome/core/hal.h" -#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -61,6 +59,9 @@ const LogString *reject_reason_for(int err) { size_t format_reject_payload(uint8_t *buf, size_t capacity, const LogString *reason) { if (capacity == 0) { + // A caller bug; the MAC_FAILURE_PAYLOAD_SIZE static_asserts at the call + // sites make this unreachable, kept as cheap memory safety + ESP_LOGVV(TAG, "Reject buffer has no capacity"); return 0; } buf[0] = HANDSHAKE_STATUS_REJECT; @@ -83,15 +84,5 @@ size_t format_reject_payload(uint8_t *buf, size_t capacity, const LogString *rea return reason_len + 1; } -extern "C" { -// declare how noise generates random bytes (here with a good HWRNG based on the RF system) -void noise_rand_bytes(void *output, size_t len) { - if (!esphome::random_bytes(reinterpret_cast(output), len)) { - ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting"); - arch_restart(); - } -} -} - } // namespace esphome::noise #endif // USE_NOISE diff --git a/esphome/components/noise/noise.h b/esphome/components/noise/noise.h index 3f3074e2d4..a7f5d8fe91 100644 --- a/esphome/components/noise/noise.h +++ b/esphome/components/noise/noise.h @@ -63,5 +63,11 @@ size_t format_reject_payload(uint8_t *buf, size_t capacity, const LogString *rea /// wire contract: clients match it to report a wrong key. const LogString *reject_reason_for(int err); +/// Payload size of the MAC failure reject, the one reason string that is a +/// wire contract (sizeof's NUL stands in for the status byte). static_assert +/// reject buffers against this so a wrong key report can never truncate; +/// longer caller-supplied reasons are informational and sized by the caller. +static constexpr size_t MAC_FAILURE_PAYLOAD_SIZE = sizeof("Handshake MAC failure"); + } // namespace esphome::noise #endif // USE_NOISE diff --git a/esphome/components/noise/noise_handshake.cpp b/esphome/components/noise/noise_handshake.cpp index 92dbf54050..6d426de012 100644 --- a/esphome/components/noise/noise_handshake.cpp +++ b/esphome/components/noise/noise_handshake.cpp @@ -1,8 +1,18 @@ #include "noise_handshake.h" #ifdef USE_NOISE +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome::noise { +static const char *const TAG = "noise"; + +// Log the failing noise-c call at the same verbosity the api helper used +// before this class existed; callers only see one collapsed error code. +#define HANDSHAKE_STEP_LOG(func_name, err_code) \ + ESP_LOGVV(TAG, "%s failed: %s", LOG_STR_ARG(LOG_STR(func_name)), LOG_STR_ARG(noise_err_to_logstr(err_code))) + NoiseResponderHandshake::~NoiseResponderHandshake() { if (this->handshake_ != nullptr) { noise_handshakestate_free(this->handshake_); @@ -11,6 +21,10 @@ NoiseResponderHandshake::~NoiseResponderHandshake() { } int NoiseResponderHandshake::init(const psk_t &psk, const uint8_t *prologue, size_t prologue_len) { + if (this->handshake_ != nullptr) { + noise_handshakestate_free(this->handshake_); + this->handshake_ = nullptr; + } // Noise_NNpsk0_25519_ChaChaPoly_SHA256, built on the stack: // noise_handshakestate_new_by_id copies it, so a member would waste // 104 bytes per connection, and a static const would sit in RAM on @@ -26,38 +40,66 @@ int NoiseResponderHandshake::init(const psk_t &psk, const uint8_t *prologue, siz }; int err = noise_handshakestate_new_by_id(&this->handshake_, &nid, NOISE_ROLE_RESPONDER); - if (err != 0) + if (err != 0) { + HANDSHAKE_STEP_LOG("noise_handshakestate_new_by_id", err); return err; + } err = noise_handshakestate_set_pre_shared_key(this->handshake_, psk.data(), psk.size()); - if (err != 0) - return err; + if (err != 0) { + HANDSHAKE_STEP_LOG("noise_handshakestate_set_pre_shared_key", err); + return this->fail_init_(err); + } err = noise_handshakestate_set_prologue(this->handshake_, prologue, prologue_len); - if (err != 0) - return err; - return noise_handshakestate_start(this->handshake_); + if (err != 0) { + HANDSHAKE_STEP_LOG("noise_handshakestate_set_prologue", err); + return this->fail_init_(err); + } + err = noise_handshakestate_start(this->handshake_); + if (err != 0) { + HANDSHAKE_STEP_LOG("noise_handshakestate_start", err); + return this->fail_init_(err); + } + return 0; +} + +/// Release a half-initialized state so a failed init() leaves the object as +/// if init() was never called. +int NoiseResponderHandshake::fail_init_(int err) { + noise_handshakestate_free(this->handshake_); + this->handshake_ = nullptr; + return err; } NoiseResponderHandshake::Action NoiseResponderHandshake::action() const { - switch (noise_handshakestate_get_action(this->handshake_)) { + if (this->handshake_ == nullptr) { + // A caller bug: init() was never called, or split() already released the state + ESP_LOGVV(TAG, "action() on uninitialized or split handshake"); + return Action::ACTION_FAILED; + } + int raw = noise_handshakestate_get_action(this->handshake_); + switch (raw) { case NOISE_ACTION_READ_MESSAGE: - return Action::READ; + return Action::ACTION_READ; case NOISE_ACTION_WRITE_MESSAGE: - return Action::WRITE; + return Action::ACTION_WRITE; case NOISE_ACTION_SPLIT: - return Action::SPLIT; + return Action::ACTION_SPLIT; default: - return Action::FAILED; + // Preserve the raw code in debug logs; callers only see the collapsed enum + ESP_LOGVV(TAG, "Unexpected noise action %d", raw); + return Action::ACTION_FAILED; } } -int NoiseResponderHandshake::read_message(const uint8_t *data, size_t len) { +int NoiseResponderHandshake::read_message(uint8_t *data, size_t len) { NoiseBuffer mbuf; noise_buffer_init(mbuf); - noise_buffer_set_input(mbuf, const_cast(data), len); + noise_buffer_set_input(mbuf, data, len); return noise_handshakestate_read_message(this->handshake_, &mbuf, nullptr); } int NoiseResponderHandshake::write_message(uint8_t *out, size_t capacity, size_t &out_len) { + out_len = 0; NoiseBuffer mbuf; noise_buffer_init(mbuf); noise_buffer_set_output(mbuf, out, capacity); @@ -68,6 +110,11 @@ int NoiseResponderHandshake::write_message(uint8_t *out, size_t capacity, size_t } int NoiseResponderHandshake::split(NoiseCipherState *&send_cipher, NoiseCipherState *&recv_cipher) { + // Defined error postcondition: noise-c leaves the out-params unwritten on + // its early error returns, so a caller passing uninitialized locals must + // never see garbage to free + send_cipher = nullptr; + recv_cipher = nullptr; int err = noise_handshakestate_split(this->handshake_, &send_cipher, &recv_cipher); if (err != 0) return err; @@ -76,5 +123,17 @@ int NoiseResponderHandshake::split(NoiseCipherState *&send_cipher, NoiseCipherSt return 0; } +extern "C" { +// noise-c's only randomness source (the vendored library compiles no rand of +// its own); HWRNG backed. Lives in this TU so every handshake consumer links +// it and the definition can never be dropped from the archive. +void noise_rand_bytes(void *output, size_t len) { + if (!esphome::random_bytes(reinterpret_cast(output), len)) { + ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting"); + arch_restart(); + } +} +} + } // namespace esphome::noise #endif // USE_NOISE diff --git a/esphome/components/noise/noise_handshake.h b/esphome/components/noise/noise_handshake.h index 3941e9434a..30596f35c2 100644 --- a/esphome/components/noise/noise_handshake.h +++ b/esphome/components/noise/noise_handshake.h @@ -16,7 +16,9 @@ namespace esphome::noise { * messages (no framing) over its own transport, driven by action(): * read_message() while READ, write_message() while WRITE, then split() to * take ownership of the transport ciphers. All methods return a noise-c - * error code, 0 on success. + * error code, 0 on success. Called outside their action() step (before + * init(), after split()) the message methods return a noise-c error rather + * than crashing; the library checks its state argument. * * Methods are deliberately small separate functions so callers on tight * stacks (RP2040 core0 scratch bank) never pay for more than one branch; @@ -24,22 +26,36 @@ namespace esphome::noise { */ class NoiseResponderHandshake { public: - enum class Action : uint8_t { READ, WRITE, SPLIT, FAILED }; + // The ACTION_ prefix is macro-collision safety: SDK headers #define bare + // names like READ/WRITE, and macros expand even inside an enum class. + enum class Action : uint8_t { ACTION_READ, ACTION_WRITE, ACTION_SPLIT, ACTION_FAILED }; + NoiseResponderHandshake() = default; ~NoiseResponderHandshake(); + // Owns a raw noise-c handshake state; copying would double free it + NoiseResponderHandshake(const NoiseResponderHandshake &) = delete; + NoiseResponderHandshake &operator=(const NoiseResponderHandshake &) = delete; - /// Create and start the handshake with the given PSK and prologue. - int init(const psk_t &psk, const uint8_t *prologue, size_t prologue_len); - Action action() const; - /// Process one received handshake message. - int read_message(const uint8_t *data, size_t len); - /// Produce the next handshake message into out (out_len receives its size). - int write_message(uint8_t *out, size_t capacity, size_t &out_len); + /// Create and start the handshake with the given PSK and prologue. A + /// repeated call frees the previous handshake state and starts over. + [[nodiscard]] int init(const psk_t &psk, const uint8_t *prologue, size_t prologue_len); + /// ACTION_FAILED is the catch-all: returned before init(), after split() + /// has released the state, and when noise-c reports a failed handshake. + [[nodiscard]] Action action() const; + /// Process one received handshake message. The buffer is consumed in + /// place: noise-c decrypts into it and zeroes it before returning. + [[nodiscard]] int read_message(uint8_t *data, size_t len); + /// Produce the next handshake message into out; out_len receives its size + /// and is zero on error. + [[nodiscard]] int write_message(uint8_t *out, size_t capacity, size_t &out_len); /// Hand out the transport ciphers and free the handshake state. The caller - /// owns both cipher states and must free them with noise_cipherstate_free(). - int split(NoiseCipherState *&send_cipher, NoiseCipherState *&recv_cipher); + /// owns both cipher states and must free them with noise_cipherstate_free(); + /// both are set to nullptr on error. + [[nodiscard]] int split(NoiseCipherState *&send_cipher, NoiseCipherState *&recv_cipher); protected: + int fail_init_(int err); + NoiseHandshakeState *handshake_{nullptr}; };