[noise] Generate the responder ephemeral key ahead of the handshake (#19000)

This commit is contained in:
J. Nick Koston
2026-10-01 16:15:31 +00:00
committed by GitHub
parent 56a7a11162
commit 826571518a
12 changed files with 174 additions and 4 deletions
+8
View File
@@ -1,3 +1,4 @@
import esphome.codegen as cg
from tests.testing_helpers import ComponentManifestOverride
@@ -5,3 +6,10 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
# to_code must run: it defines USE_NOISE and adds the noise-c library
# the component sources under test need.
manifest.enable_codegen()
real_to_code = manifest.to_code
async def to_code_testing(config):
await real_to_code(config)
cg.add_define("USE_NOISE_SPARE_EPHEMERAL")
manifest.to_code = to_code_testing
@@ -157,6 +157,72 @@ TEST(NoiseResponderHandshakeTest, FullHandshakeAndTransportRoundTrip) {
noise_cipherstate_free(recv_cipher);
}
// One full NNpsk0 handshake; responder_e gets the ephemeral public key the responder put on the wire, read
// before the initiator consumes the buffer in place
static void run_handshake(NoiseResponderHandshake &responder, uint8_t responder_e[SPARE_EPHEMERAL_KEY_SIZE]) {
const psk_t psk = make_psk(7);
ASSERT_EQ(responder.init(ctx_for(psk), PROLOGUE, sizeof(PROLOGUE)), 0);
Initiator initiator(psk, PROLOGUE, sizeof(PROLOGUE));
uint8_t msg[MAX_HANDSHAKE_SIZE];
size_t msg_len = initiator.write_message(msg, sizeof(msg));
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, SPARE_EPHEMERAL_KEY_SIZE);
std::memcpy(responder_e, msg, SPARE_EPHEMERAL_KEY_SIZE);
ASSERT_EQ(initiator.read_message(msg, reply_len), 0);
ASSERT_EQ(responder.action(), Action::ACTION_SPLIT);
}
TEST(SpareEphemeralTest, EmptySlotLeavesHandshakeToGenerate) {
ASSERT_FALSE(has_spare_ephemeral());
NoiseResponderHandshake responder;
uint8_t responder_e[SPARE_EPHEMERAL_KEY_SIZE];
run_handshake(responder, responder_e);
EXPECT_FALSE(has_spare_ephemeral());
}
TEST(SpareEphemeralTest, ConsumeHandsTheKeyToANewState) {
prepare_spare_ephemeral();
ASSERT_TRUE(has_spare_ephemeral());
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[SPARE_EPHEMERAL_KEY_SIZE];
std::memcpy(expected_pub, spare_ephemeral + SPARE_EPHEMERAL_KEY_SIZE, sizeof(expected_pub));
NoiseResponderHandshake first;
uint8_t responder_e[SPARE_EPHEMERAL_KEY_SIZE];
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, responder_e);
EXPECT_NE(std::memcmp(responder_e, expected_pub, sizeof(expected_pub)), 0);
EXPECT_FALSE(has_spare_ephemeral());
}
TEST(NoiseResponderHandshakeTest, ReInitRestartsHandshake) {
// The documented retry shape: a repeated init() frees the previous state
// and starts over. The first message under the new key authenticating