ESPHome 2025.8.0b1
Loading...
Searching...
No Matches
api_frame_helper_noise.cpp
Go to the documentation of this file.
2#ifdef USE_API
3#ifdef USE_API_NOISE
4#include "api_connection.h" // For ClientInfo struct
6#include "esphome/core/hal.h"
8#include "esphome/core/log.h"
9#include "proto.h"
10#include <cstring>
11#include <cinttypes>
12
13namespace esphome::api {
14
15static const char *const TAG = "api.noise";
16static const char *const PROLOGUE_INIT = "NoiseAPIInit";
17static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
18
19#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
20
21#ifdef HELPER_LOG_PACKETS
22#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
23#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
24#else
25#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
26#define LOG_PACKET_SENDING(data, len) ((void) 0)
27#endif
28
30std::string noise_err_to_str(int err) {
31 if (err == NOISE_ERROR_NO_MEMORY)
32 return "NO_MEMORY";
33 if (err == NOISE_ERROR_UNKNOWN_ID)
34 return "UNKNOWN_ID";
35 if (err == NOISE_ERROR_UNKNOWN_NAME)
36 return "UNKNOWN_NAME";
37 if (err == NOISE_ERROR_MAC_FAILURE)
38 return "MAC_FAILURE";
39 if (err == NOISE_ERROR_NOT_APPLICABLE)
40 return "NOT_APPLICABLE";
41 if (err == NOISE_ERROR_SYSTEM)
42 return "SYSTEM";
43 if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
44 return "REMOTE_KEY_REQUIRED";
45 if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
46 return "LOCAL_KEY_REQUIRED";
47 if (err == NOISE_ERROR_PSK_REQUIRED)
48 return "PSK_REQUIRED";
49 if (err == NOISE_ERROR_INVALID_LENGTH)
50 return "INVALID_LENGTH";
51 if (err == NOISE_ERROR_INVALID_PARAM)
52 return "INVALID_PARAM";
53 if (err == NOISE_ERROR_INVALID_STATE)
54 return "INVALID_STATE";
55 if (err == NOISE_ERROR_INVALID_NONCE)
56 return "INVALID_NONCE";
57 if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
58 return "INVALID_PRIVATE_KEY";
59 if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
60 return "INVALID_PUBLIC_KEY";
61 if (err == NOISE_ERROR_INVALID_FORMAT)
62 return "INVALID_FORMAT";
63 if (err == NOISE_ERROR_INVALID_SIGNATURE)
64 return "INVALID_SIGNATURE";
65 return to_string(err);
66}
67
70 APIError err = init_common_();
71 if (err != APIError::OK) {
72 return err;
73 }
74
75 // init prologue
76 size_t old_size = prologue_.size();
77 prologue_.resize(old_size + PROLOGUE_INIT_LEN);
78 std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
79
81 return APIError::OK;
82}
83// Helper for handling handshake frame errors
85 if (aerr == APIError::BAD_INDICATOR) {
86 send_explicit_handshake_reject_("Bad indicator byte");
87 } else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
88 send_explicit_handshake_reject_("Bad handshake packet len");
89 }
90 return aerr;
91}
92
93// Helper for handling noise library errors
94APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
95 if (err != 0) {
97 HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
98 return api_err;
99 }
100 return APIError::OK;
101}
102
105 // During handshake phase, process as many actions as possible until we can't progress
106 // socket_->ready() stays true until next main loop, but state_action() will return
107 // WOULD_BLOCK when no more data is available to read
108 while (state_ != State::DATA && this->socket_->ready()) {
109 APIError err = state_action_();
110 if (err == APIError::WOULD_BLOCK) {
111 break;
112 }
113 if (err != APIError::OK) {
114 return err;
115 }
116 }
117
118 // Use base class implementation for buffer sending
119 return APIFrameHelper::loop();
120}
121
136APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
137 if (frame == nullptr) {
138 HELPER_LOG("Bad argument for try_read_frame_");
139 return APIError::BAD_ARG;
140 }
141
142 // read header
143 if (rx_header_buf_len_ < 3) {
144 // no header information yet
145 uint8_t to_read = 3 - rx_header_buf_len_;
146 ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
147 APIError err = handle_socket_read_result_(received);
148 if (err != APIError::OK) {
149 return err;
150 }
151 rx_header_buf_len_ += static_cast<uint8_t>(received);
152 if (static_cast<uint8_t>(received) != to_read) {
153 // not a full read
155 }
156
157 if (rx_header_buf_[0] != 0x01) {
159 HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
161 }
162 // header reading done
163 }
164
165 // read body
166 uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
167
168 if (state_ != State::DATA && msg_size > 128) {
169 // for handshake message only permit up to 128 bytes
171 HELPER_LOG("Bad packet len for handshake: %d", msg_size);
173 }
174
175 // reserve space for body
176 if (rx_buf_.size() != msg_size) {
177 rx_buf_.resize(msg_size);
178 }
179
180 if (rx_buf_len_ < msg_size) {
181 // more data to read
182 uint16_t to_read = msg_size - rx_buf_len_;
183 ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
184 APIError err = handle_socket_read_result_(received);
185 if (err != APIError::OK) {
186 return err;
187 }
188 rx_buf_len_ += static_cast<uint16_t>(received);
189 if (static_cast<uint16_t>(received) != to_read) {
190 // not all read
192 }
193 }
194
195 LOG_PACKET_RECEIVED(rx_buf_);
196 *frame = std::move(rx_buf_);
197 // consume msg
198 rx_buf_ = {};
199 rx_buf_len_ = 0;
201 return APIError::OK;
202}
203
214 int err;
215 APIError aerr;
216 if (state_ == State::INITIALIZE) {
217 HELPER_LOG("Bad state for method: %d", (int) state_);
218 return APIError::BAD_STATE;
219 }
221 // waiting for client hello
222 std::vector<uint8_t> frame;
223 aerr = try_read_frame_(&frame);
224 if (aerr != APIError::OK) {
226 }
227 // ignore contents, may be used in future for flags
228 // Resize for: existing prologue + 2 size bytes + frame data
229 size_t old_size = prologue_.size();
230 prologue_.resize(old_size + 2 + frame.size());
231 prologue_[old_size] = (uint8_t) (frame.size() >> 8);
232 prologue_[old_size + 1] = (uint8_t) frame.size();
233 std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
234
236 }
238 // send server hello
239 const std::string &name = App.get_name();
240 const std::string &mac = get_mac_address();
241
242 std::vector<uint8_t> msg;
243 // Calculate positions and sizes
244 size_t name_len = name.size() + 1; // including null terminator
245 size_t mac_len = mac.size() + 1; // including null terminator
246 size_t name_offset = 1;
247 size_t mac_offset = name_offset + name_len;
248 size_t total_size = 1 + name_len + mac_len;
249
250 msg.resize(total_size);
251
252 // chosen proto
253 msg[0] = 0x01;
254
255 // node name, terminated by null byte
256 std::memcpy(msg.data() + name_offset, name.c_str(), name_len);
257 // node mac, terminated by null byte
258 std::memcpy(msg.data() + mac_offset, mac.c_str(), mac_len);
259
260 aerr = write_frame_(msg.data(), msg.size());
261 if (aerr != APIError::OK)
262 return aerr;
263
264 // start handshake
265 aerr = init_handshake_();
266 if (aerr != APIError::OK)
267 return aerr;
268
270 }
271 if (state_ == State::HANDSHAKE) {
272 int action = noise_handshakestate_get_action(handshake_);
273 if (action == NOISE_ACTION_READ_MESSAGE) {
274 // waiting for handshake msg
275 std::vector<uint8_t> frame;
276 aerr = try_read_frame_(&frame);
277 if (aerr != APIError::OK) {
279 }
280
281 if (frame.empty()) {
282 send_explicit_handshake_reject_("Empty handshake message");
284 } else if (frame[0] != 0x00) {
285 HELPER_LOG("Bad handshake error byte: %u", frame[0]);
286 send_explicit_handshake_reject_("Bad handshake error byte");
288 }
289
290 NoiseBuffer mbuf;
291 noise_buffer_init(mbuf);
292 noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
293 err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
294 if (err != 0) {
295 // Special handling for MAC failure
296 send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
297 return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
298 }
299
301 if (aerr != APIError::OK)
302 return aerr;
303 } else if (action == NOISE_ACTION_WRITE_MESSAGE) {
304 uint8_t buffer[65];
305 NoiseBuffer mbuf;
306 noise_buffer_init(mbuf);
307 noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
308
309 err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
310 APIError aerr_write =
311 handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
312 if (aerr_write != APIError::OK)
313 return aerr_write;
314 buffer[0] = 0x00; // success
315
316 aerr = write_frame_(buffer, mbuf.size + 1);
317 if (aerr != APIError::OK)
318 return aerr;
320 if (aerr != APIError::OK)
321 return aerr;
322 } else {
323 // bad state for action
325 HELPER_LOG("Bad action for handshake: %d", action);
327 }
328 }
330 return APIError::BAD_STATE;
331 }
332 return APIError::OK;
333}
335 std::vector<uint8_t> data;
336 data.resize(reason.length() + 1);
337 data[0] = 0x01; // failure
338
339 // Copy error message in bulk
340 if (!reason.empty()) {
341 std::memcpy(data.data() + 1, reason.c_str(), reason.length());
342 }
343
344 // temporarily remove failed state
345 auto orig_state = state_;
347 write_frame_(data.data(), data.size());
348 state_ = orig_state;
349}
351 int err;
352 APIError aerr;
353 aerr = state_action_();
354 if (aerr != APIError::OK) {
355 return aerr;
356 }
357
358 if (state_ != State::DATA) {
360 }
361
362 std::vector<uint8_t> frame;
363 aerr = try_read_frame_(&frame);
364 if (aerr != APIError::OK)
365 return aerr;
366
367 NoiseBuffer mbuf;
368 noise_buffer_init(mbuf);
369 noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
370 err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
371 APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
372 if (decrypt_err != APIError::OK)
373 return decrypt_err;
374
375 uint16_t msg_size = mbuf.size;
376 uint8_t *msg_data = frame.data();
377 if (msg_size < 4) {
379 HELPER_LOG("Bad data packet: size %d too short", msg_size);
381 }
382
383 uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
384 uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
385 if (data_len > msg_size - 4) {
387 HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
389 }
390
391 buffer->container = std::move(frame);
392 buffer->data_offset = 4;
393 buffer->data_len = data_len;
394 buffer->type = type;
395 return APIError::OK;
396}
398 // Resize to include MAC space (required for Noise encryption)
399 buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
400 PacketInfo packet{type, 0,
401 static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
402 return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
403}
404
405APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
406 APIError aerr = state_action_();
407 if (aerr != APIError::OK) {
408 return aerr;
409 }
410
411 if (state_ != State::DATA) {
413 }
414
415 if (packets.empty()) {
416 return APIError::OK;
417 }
418
419 std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
420 uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
421
422 this->reusable_iovs_.clear();
423 this->reusable_iovs_.reserve(packets.size());
424 uint16_t total_write_len = 0;
425
426 // We need to encrypt each packet in place
427 for (const auto &packet : packets) {
428 // The buffer already has padding at offset
429 uint8_t *buf_start = buffer_data + packet.offset;
430
431 // Write noise header
432 buf_start[0] = 0x01; // indicator
433 // buf_start[1], buf_start[2] to be set after encryption
434
435 // Write message header (to be encrypted)
436 const uint8_t msg_offset = 3;
437 buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
438 buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
439 buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
440 buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
441 // payload data is already in the buffer starting at offset + 7
442
443 // Make sure we have space for MAC
444 // The buffer should already have been sized appropriately
445
446 // Encrypt the message in place
447 NoiseBuffer mbuf;
448 noise_buffer_init(mbuf);
449 noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
450 4 + packet.payload_size + frame_footer_size_);
451
452 int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
453 APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
454 if (aerr != APIError::OK)
455 return aerr;
456
457 // Fill in the encrypted size
458 buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
459 buf_start[2] = static_cast<uint8_t>(mbuf.size);
460
461 // Add iovec for this encrypted packet
462 size_t packet_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
463 this->reusable_iovs_.push_back({buf_start, packet_len});
464 total_write_len += packet_len;
465 }
466
467 // Send all encrypted packets in one writev call
468 return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
469}
470
471APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
472 uint8_t header[3];
473 header[0] = 0x01; // indicator
474 header[1] = (uint8_t) (len >> 8);
475 header[2] = (uint8_t) len;
476
477 struct iovec iov[2];
478 iov[0].iov_base = header;
479 iov[0].iov_len = 3;
480 if (len == 0) {
481 return this->write_raw_(iov, 1, 3); // Just header
482 }
483 iov[1].iov_base = const_cast<uint8_t *>(data);
484 iov[1].iov_len = len;
485
486 return this->write_raw_(iov, 2, 3 + len); // Header + data
487}
488
494 int err;
495 memset(&nid_, 0, sizeof(nid_));
496 // const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
497 // err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
498 nid_.pattern_id = NOISE_PATTERN_NN;
499 nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
500 nid_.dh_id = NOISE_DH_CURVE25519;
501 nid_.prefix_id = NOISE_PREFIX_STANDARD;
502 nid_.hybrid_id = NOISE_DH_NONE;
503 nid_.hash_id = NOISE_HASH_SHA256;
504 nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
505
506 err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
507 APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
508 if (aerr != APIError::OK)
509 return aerr;
510
511 const auto &psk = ctx_->get_psk();
512 err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
513 aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
514 if (aerr != APIError::OK)
515 return aerr;
516
517 err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
518 aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
519 if (aerr != APIError::OK)
520 return aerr;
521 // set_prologue copies it into handshakestate, so we can get rid of it now
522 prologue_ = {};
523
524 err = noise_handshakestate_start(handshake_);
525 aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
526 if (aerr != APIError::OK)
527 return aerr;
528 return APIError::OK;
529}
530
532 assert(state_ == State::HANDSHAKE);
533
534 int action = noise_handshakestate_get_action(handshake_);
535 if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
536 return APIError::OK;
537 if (action != NOISE_ACTION_SPLIT) {
539 HELPER_LOG("Bad action for handshake: %d", action);
541 }
542 int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
543 APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
544 if (aerr != APIError::OK)
545 return aerr;
546
547 frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
548
549 HELPER_LOG("Handshake complete!");
550 noise_handshakestate_free(handshake_);
551 handshake_ = nullptr;
553 return APIError::OK;
554}
555
557 if (handshake_ != nullptr) {
558 noise_handshakestate_free(handshake_);
559 handshake_ = nullptr;
560 }
561 if (send_cipher_ != nullptr) {
562 noise_cipherstate_free(send_cipher_);
563 send_cipher_ = nullptr;
564 }
565 if (recv_cipher_ != nullptr) {
566 noise_cipherstate_free(recv_cipher_);
567 recv_cipher_ = nullptr;
568 }
569}
570
571extern "C" {
572// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
573void noise_rand_bytes(void *output, size_t len) {
574 if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
575 ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting");
576 arch_restart();
577 }
578}
579}
580
581} // namespace esphome::api
582#endif // USE_API_NOISE
583#endif // USE_API
const std::string & get_name() const
Get the name of this Application set by pre_setup().
APIError handle_socket_read_result_(ssize_t received)
std::vector< uint8_t > rx_buf_
std::vector< struct iovec > reusable_iovs_
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len)
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override
APIError read_packet(ReadPacketBuffer *buffer) override
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span< const PacketInfo > packets) override
APIError try_read_frame_(std::vector< uint8_t > *frame)
Read a packet into the rx_buf_.
APIError state_action_()
To be called from read/write methods.
APIError handle_noise_error_(int err, const char *func_name, APIError api_err)
APIError loop() override
Run through handshake messages (if in that phase)
APIError handle_handshake_frame_error_(APIError aerr)
APIError write_frame_(const uint8_t *data, uint16_t len)
std::shared_ptr< APINoiseContext > ctx_
APIError init() override
Initialize the frame helper, returns OK if successful.
void send_explicit_handshake_reject_(const std::string &reason)
APIError init_handshake_()
Initiate the data structures for the handshake.
std::vector< uint8_t > * get_buffer() const
Definition proto.h:327
bool ready() const
Check if socket has data ready to read For loop-monitored sockets, checks with the Application's sele...
Definition socket.cpp:14
virtual ssize_t read(void *buf, size_t len)=0
uint8_t type
__int64 ssize_t
Definition httplib.h:175
void noise_rand_bytes(void *output, size_t len)
std::string noise_err_to_str(int err)
Convert a noise error code to a readable error.
bool random_bytes(uint8_t *data, size_t len)
Generate len number of random bytes.
Definition helpers.cpp:18
std::string size_t len
Definition helpers.h:279
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition helpers.cpp:578
void arch_restart()
Definition core.cpp:32
Application App
Global storage of Application pointer - only one Application can exist.
std::vector< uint8_t > container
void * iov_base
Definition headers.h:101
size_t iov_len
Definition headers.h:102