mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
[noise] Consume the spare key inside the handshake and let noise own its define
NoiseResponderHandshake::init() now hands the slot straight to noise-c and wipes it, so the api and ota call sites are unchanged, nothing copies the key pair and no transport has to remember the wipe. The slot compiles under USE_NOISE_SPARE_EPHEMERAL, which the api component enables as the refiller, instead of the noise component keying on an api define. The per tick check sits in loop() with the refill out of line, and the grace predicate lives next to the handshake timeout it mirrors.
This commit is contained in:
@@ -10,7 +10,6 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
|
||||
async def to_code_testing(config):
|
||||
await real_to_code(config)
|
||||
# The spare ephemeral slot only exists in builds with an encrypted api
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_define("USE_NOISE_SPARE_EPHEMERAL")
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <noise/protocol.h>
|
||||
#include <sodium.h>
|
||||
|
||||
#include "esphome/components/noise/noise.h"
|
||||
#include "esphome/components/noise/noise_handshake.h"
|
||||
@@ -158,59 +157,36 @@ TEST(NoiseResponderHandshakeTest, FullHandshakeAndTransportRoundTrip) {
|
||||
noise_cipherstate_free(recv_cipher);
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, EmptySlotHandsOutNothing) {
|
||||
ephemeral_keypair_t out;
|
||||
// Drain whatever an earlier test left behind, then the slot must stay empty
|
||||
take_spare_ephemeral(out);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
EXPECT_FALSE(take_spare_ephemeral(out));
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, KeyPairIsHandedOutExactlyOnce) {
|
||||
ephemeral_keypair_t out;
|
||||
take_spare_ephemeral(out);
|
||||
prepare_spare_ephemeral();
|
||||
ASSERT_TRUE(has_spare_ephemeral());
|
||||
ASSERT_TRUE(take_spare_ephemeral(out));
|
||||
// Taken once: the slot is empty and a second take gets nothing
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
EXPECT_FALSE(take_spare_ephemeral(out));
|
||||
|
||||
// The pair is consistent: the public half is the base point multiple of the private half
|
||||
uint8_t check[EPHEMERAL_PUBLIC_KEY_SIZE];
|
||||
ASSERT_EQ(crypto_scalarmult_curve25519_base(check, out.data()), 0);
|
||||
EXPECT_EQ(std::memcmp(check, out.data() + EPHEMERAL_PRIVATE_KEY_SIZE, EPHEMERAL_PUBLIC_KEY_SIZE), 0);
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, SuppliedKeyPairCompletesHandshakeAndIsTheKeyOnTheWire) {
|
||||
ephemeral_keypair_t spare;
|
||||
take_spare_ephemeral(spare);
|
||||
prepare_spare_ephemeral();
|
||||
ASSERT_TRUE(take_spare_ephemeral(spare));
|
||||
|
||||
// Drive one full NNpsk0 handshake between a fresh initiator and responder
|
||||
static void run_handshake(NoiseResponderHandshake &responder) {
|
||||
const psk_t psk = make_psk(7);
|
||||
NoiseResponderHandshake responder;
|
||||
ASSERT_EQ(responder.init(ctx_for(psk), PROLOGUE, sizeof(PROLOGUE), spare.data()), 0);
|
||||
|
||||
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);
|
||||
// The responder's message starts with its ephemeral public key
|
||||
ASSERT_GE(reply_len, static_cast<size_t>(EPHEMERAL_PUBLIC_KEY_SIZE));
|
||||
EXPECT_EQ(std::memcmp(msg, spare.data() + EPHEMERAL_PRIVATE_KEY_SIZE, EPHEMERAL_PUBLIC_KEY_SIZE), 0);
|
||||
|
||||
ASSERT_EQ(initiator.read_message(msg, reply_len), 0);
|
||||
initiator.split();
|
||||
NoiseCipherState *send_cipher = nullptr;
|
||||
NoiseCipherState *recv_cipher = nullptr;
|
||||
ASSERT_EQ(responder.split(send_cipher, recv_cipher), 0);
|
||||
ASSERT_NE(send_cipher, nullptr);
|
||||
noise_cipherstate_free(send_cipher);
|
||||
noise_cipherstate_free(recv_cipher);
|
||||
ASSERT_EQ(responder.action(), Action::ACTION_SPLIT);
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, EmptySlotLeavesHandshakeToGenerate) {
|
||||
NoiseResponderHandshake responder;
|
||||
run_handshake(responder);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, SlotIsConsumedByExactlyOneHandshake) {
|
||||
prepare_spare_ephemeral();
|
||||
ASSERT_TRUE(has_spare_ephemeral());
|
||||
NoiseResponderHandshake first;
|
||||
run_handshake(first);
|
||||
// Consumed: the next handshake finds no spare and still completes
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
NoiseResponderHandshake second;
|
||||
run_handshake(second);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
}
|
||||
|
||||
TEST(NoiseResponderHandshakeTest, ReInitRestartsHandshake) {
|
||||
|
||||
Reference in New Issue
Block a user