mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
[noise] Assert the spare key reaches the wire, drop the ESP8266 warning threshold
The gtest now checks that the responder's message carries the slot's public key and that the next handshake uses a different one, so a silently refused spare cannot pass. The api refill sites are guarded by the feature define they use. The ESP8266 blocking threshold change is left to a separate core change for operations that cannot be shortened.
This commit is contained in:
@@ -41,11 +41,6 @@ void APIServer::setup() {
|
||||
ControllerRegistry::register_controller(this);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
#ifdef USE_ESP8266
|
||||
// The spare ephemeral refill blocks ~60 ms here and shares the pass with
|
||||
// the client loops; keep the whole pass under the blocking warning
|
||||
this->warn_if_blocking_over_ = 10; // centiseconds
|
||||
#endif
|
||||
// Always reserve the slot: flash preferences are positional on esp8266, so
|
||||
// a yaml key build must keep the layout of a runtime key build
|
||||
uint32_t hash = 88491486UL;
|
||||
@@ -150,7 +145,7 @@ void APIServer::loop() {
|
||||
|
||||
// Checked once per pass for the refill and for the clients below
|
||||
const bool connected = network::is_connected();
|
||||
#ifdef USE_API_NOISE
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
// Only the flag test is inline; refilling is the rare path
|
||||
if (connected && !noise::has_spare_ephemeral()) {
|
||||
this->refill_spare_ephemeral_();
|
||||
@@ -201,7 +196,7 @@ void APIServer::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
// Called with the network up; refill only while no api client is still
|
||||
// connecting (an OTA handshake is not visible here and just pays the refill
|
||||
// it triggered).
|
||||
|
||||
@@ -363,8 +363,10 @@ class APIServer final : public Component,
|
||||
uint8_t provisioning_source_{0};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
void refill_spare_ephemeral_();
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
noise::NoiseContext noise_ctx_;
|
||||
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||
SavedNoisePsk saved_psk_{}; // backs noise_ctx_ for a runtime provisioned key
|
||||
|
||||
@@ -157,8 +157,11 @@ TEST(NoiseResponderHandshakeTest, FullHandshakeAndTransportRoundTrip) {
|
||||
noise_cipherstate_free(recv_cipher);
|
||||
}
|
||||
|
||||
// Drive one full NNpsk0 handshake between a fresh initiator and responder
|
||||
static void run_handshake(NoiseResponderHandshake &responder) {
|
||||
// Drive one full NNpsk0 handshake between a fresh initiator and responder;
|
||||
// responder_e receives the ephemeral public key the responder put on the
|
||||
// wire (the clear text start of its message, taken before the initiator
|
||||
// consumes the buffer in place)
|
||||
static void run_handshake(NoiseResponderHandshake &responder, uint8_t responder_e[32]) {
|
||||
const psk_t psk = make_psk(7);
|
||||
ASSERT_EQ(responder.init(ctx_for(psk), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
Initiator initiator(psk, PROLOGUE, sizeof(PROLOGUE));
|
||||
@@ -167,25 +170,57 @@ static void run_handshake(NoiseResponderHandshake &responder) {
|
||||
ASSERT_EQ(responder.read_message(msg, msg_len), 0);
|
||||
size_t reply_len = 0;
|
||||
ASSERT_EQ(responder.write_message(msg, sizeof(msg), reply_len), 0);
|
||||
ASSERT_GE(reply_len, 32u);
|
||||
std::memcpy(responder_e, msg, 32);
|
||||
ASSERT_EQ(initiator.read_message(msg, reply_len), 0);
|
||||
ASSERT_EQ(responder.action(), Action::ACTION_SPLIT);
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, EmptySlotLeavesHandshakeToGenerate) {
|
||||
NoiseResponderHandshake responder;
|
||||
run_handshake(responder);
|
||||
uint8_t responder_e[32];
|
||||
run_handshake(responder, responder_e);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, SlotIsConsumedByExactlyOneHandshake) {
|
||||
TEST(SpareEphemeralTest, ConsumeHandsTheKeyToANewState) {
|
||||
prepare_spare_ephemeral();
|
||||
ASSERT_TRUE(has_spare_ephemeral());
|
||||
NoiseResponderHandshake first;
|
||||
run_handshake(first);
|
||||
// Consumed: the next handshake finds no spare and still completes
|
||||
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,
|
||||
};
|
||||
NoiseHandshakeState *state = nullptr;
|
||||
ASSERT_EQ(noise_handshakestate_new_by_id(&state, &nid, NOISE_ROLE_RESPONDER), 0);
|
||||
const psk_t psk = make_psk(7);
|
||||
ASSERT_EQ(noise_handshakestate_set_pre_shared_key(state, psk.data(), psk.size()), 0);
|
||||
ASSERT_EQ(noise_handshakestate_set_prologue(state, PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
EXPECT_EQ(consume_spare_ephemeral(state), 0);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
noise_handshakestate_free(state);
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, SlotKeyIsOnTheWireAndConsumedOnce) {
|
||||
prepare_spare_ephemeral();
|
||||
ASSERT_TRUE(has_spare_ephemeral());
|
||||
uint8_t expected_pub[32];
|
||||
std::memcpy(expected_pub, spare_ephemeral + 32, sizeof(expected_pub));
|
||||
|
||||
NoiseResponderHandshake first;
|
||||
uint8_t responder_e[32];
|
||||
run_handshake(first, responder_e);
|
||||
// The spare, not a generated key, went out; and it went out once
|
||||
EXPECT_EQ(std::memcmp(responder_e, expected_pub, sizeof(expected_pub)), 0);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
|
||||
NoiseResponderHandshake second;
|
||||
run_handshake(second);
|
||||
run_handshake(second, responder_e);
|
||||
EXPECT_NE(std::memcmp(responder_e, expected_pub, sizeof(expected_pub)), 0);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user