[noise] Trim comments

This commit is contained in:
J. Nick Koston
2026-08-24 14:31:35 -05:00
parent 34caf86839
commit b8a79a26fb
2 changed files with 17 additions and 36 deletions
@@ -267,9 +267,8 @@ APIError APINoiseFrameHelper::state_action_client_hello_() {
if (aerr != APIError::OK) {
return handle_handshake_frame_error_(aerr);
}
// Contents are extension flags; today the only defined extension is the
// session resume offer. Everything is mixed into the prologue either way.
// Resize for: existing prologue + 2 size bytes + frame data
// Contents are extension flags (today: the resume offer); mixed into the
// prologue either way. Resize for: existing prologue + 2 size bytes + frame data
size_t old_size = this->prologue_.size();
size_t rx_size = this->rx_buf_.size();
this->prologue_.resize(old_size + 2 + rx_size);
@@ -283,12 +282,8 @@ APIError APINoiseFrameHelper::state_action_client_hello_() {
return APIError::OK;
}
APIError APINoiseFrameHelper::state_action_server_hello_() {
// A verified resume offer (still in rx_buf_ from the client hello step; no
// frame is read in between) replaces the whole handshake: the cache
// consumes the ticket, proves possession of its secret in a trailing
// ServerHello extension (old clients ignore trailing bytes), and hands
// back ready transport ciphers. Every failure silently falls back to the
// full handshake.
// A verified resume offer (still in rx_buf_ from the client hello step)
// replaces the whole handshake; any failure falls back to the full one.
uint8_t resume_ext[noise::RESUME_ACCEPT_SIZE];
bool resume = this->ctx_.resume_cache().try_accept(this->rx_buf_.data(), this->rx_buf_.size(), this->prologue_.data(),
this->prologue_.size(), resume_ext, send_cipher_, recv_cipher_);
@@ -329,8 +324,6 @@ APIError APINoiseFrameHelper::state_action_server_hello_() {
return aerr;
if (resume) {
// The client's full-handshake message 1 is already in flight; discard
// it before entering DATA.
this->prologue_.release();
this->frame_footer_size_ = noise_cipherstate_get_mac_length(this->send_cipher_);
state_ = State::RESUME_DISCARD;
@@ -357,9 +350,8 @@ APIError APINoiseFrameHelper::state_action_handshake_() {
HELPER_LOG("Bad action for handshake: %d", (int) action);
return APIError::HANDSHAKESTATE_BAD_STATE;
}
/// Resumed session: read and discard the client's full-handshake message 1,
/// which was already in flight when the resume offer was accepted, then
/// enter DATA. The same status byte rules apply as for a real handshake read.
/// Resumed session: discard the client's already-in-flight handshake
/// message 1, then enter DATA.
APIError APINoiseFrameHelper::state_action_resume_discard_() {
APIError aerr = this->try_read_frame_();
if (aerr != APIError::OK) {
+11 -22
View File
@@ -14,23 +14,16 @@ namespace esphome::noise {
/** Session resume for the noise transports.
*
* After a full NNpsk0 handshake the responder issues a single-use ticket
* (session id + secret) over the encrypted channel. A client holding a
* ticket places a resume offer in its ClientHello; the responder proves
* possession of the secret in its ServerHello and both sides derive the
* transport keys with HKDF-SHA256 alone, skipping the two curve25519
* operations of a full handshake (~37 ms on ESP32, ~290 ms on ESP8266 at
* 80 MHz). Old peers ignore the extension bytes on both sides, so every
* mismatch degrades to a normal full handshake on the same connection.
*
* All HKDF calls use the Noise construction (noise_hashstate_hkdf):
* temp = HMAC-SHA256(key, data); out1 = HMAC(temp, 0x01);
* out2 = HMAC(temp, out1 || 0x02).
* After a full handshake the responder issues a single-use ticket over the
* encrypted channel. A client presents it in its next ClientHello and both
* sides derive the transport keys with HKDF-SHA256 alone, skipping the two
* curve25519 operations. Old peers ignore the extension bytes on both
* sides, so every mismatch degrades to a normal full handshake.
*
* HKDF is the Noise construction (noise_hashstate_hkdf). Derivations:
* offer_mac = HKDF(secret, "offer" || session_id || client_nonce).out1[:16]
* confirm_mac = HKDF(secret, "confirm" || client_nonce || server_nonce).out1[:16]
* k_c2d, k_d2c = HKDF(secret, "keys" || client_nonce || server_nonce
* || SHA256(prologue)) (32 bytes each)
* k_c2d, k_d2c = HKDF(secret, "keys" || client_nonce || server_nonce || SHA256(prologue))
*/
static constexpr uint8_t RESUME_OFFER_VERSION = 0x01;
@@ -61,14 +54,10 @@ class ResumeTicketCache {
/// Generate a fresh ticket into out and store it, evicting the oldest
/// slot. Returns false (and stores nothing) if the RNG fails.
bool issue(ResumeTicket &out);
/// Try to accept a resume offer: recognizes the offer format, verifies
/// its MAC against a cached ticket (consuming it, single use), derives
/// the transport keys bound to the prologue, builds both ChaChaPoly
/// cipher states, and fills the RESUME_ACCEPT_SIZE ServerHello extension
/// proving possession of the secret. A miss, a bad MAC, or any internal
/// failure returns false with nothing allocated, and a forged offer never
/// burns a ticket. k_c2d decrypts client-to-device traffic (recv), k_d2c
/// encrypts device-to-client (send). All secrets are wiped internally.
/// Accept a resume offer: verify and consume the ticket (single use; a
/// forged MAC never burns one), build both transport ciphers, and fill
/// the RESUME_ACCEPT_SIZE ServerHello extension. Returns false with
/// nothing allocated on any miss or failure. Secrets are wiped internally.
bool try_accept(const uint8_t *offer, size_t offer_len, const uint8_t *prologue, size_t prologue_len,
uint8_t *out_ext, NoiseCipherState *&send_cipher, NoiseCipherState *&recv_cipher);
/// Forget every ticket (PSK change).