From cecd0e94e0aa84b71dcd1df8595193c249ed39d0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 22 May 2026 12:58:37 -0500 Subject: [PATCH] Refine unsigned packet rejection logic in Router --- src/mesh/CryptoEngine.cpp | 5 +++-- src/mesh/NodeDB.cpp | 14 +++++++++----- src/mesh/NodeDB.h | 2 +- src/mesh/Router.cpp | 10 +++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index b3e9e02cf..3b01afd4a 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -133,8 +133,9 @@ void CryptoEngine::curve_to_ed_pub(const uint8_t *curve_pubkey, uint8_t *ed_pubk // key from a Curve25519 public key. Because the serialization format of Curve25519 public keys only // contains the u coordinate, the x coordinate of the corresponding Ed25519 public key can't be uniquely // calculated as defined by the birational map. The x coordinate is represented in the serialization - // format of Ed25519 public keys only in a single sign bit. This function assumes that the sign bit is - // known to the user and is passed accordingly. + // format of Ed25519 public keys only in a single sign bit. XEdDSA always normalizes the Ed25519 public + // key to a sign bit of zero (the signer negates its key pair when needed), so this function clears the + // sign bit unconditionally below instead of taking it as an input. fe u, y; fe one; fe u_minus_one, u_plus_one, u_plus_one_inv; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5316c49b1..a94c5fb99 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -2775,7 +2775,10 @@ bool NodeDB::generateCryptoKeyPair(const uint8_t *privateKey) } bool keygenSuccess = false; - bool lowEntropy = checkLowEntropyPublicKey(config.security.public_key); + // Record whether the stored key is a known compromised/low-entropy key so main.cpp can warn the + // user. A detected low-entropy key is regenerated below, but the flag stays set so the + // "Compromised keys were detected and regenerated" notification still fires. + keyIsLowEntropy = checkLowEntropyPublicKey(config.security.public_key); // If a specific private key was provided, use it if (privateKey != nullptr) { @@ -2793,7 +2796,7 @@ bool NodeDB::generateCryptoKeyPair(const uint8_t *privateKey) } } // Try to regenerate public key from existing private key if it's valid and not low entropy - else if (config.security.private_key.size == 32 && !lowEntropy) { + else if (config.security.private_key.size == 32 && !keyIsLowEntropy) { config.security.public_key.size = 32; LOG_DEBUG("Regenerate PKI public key from existing private key"); if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { @@ -2813,9 +2816,6 @@ bool NodeDB::generateCryptoKeyPair(const uint8_t *privateKey) owner.public_key.size = 32; memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); - // Update global entropy flag for UI display - keyIsLowEntropy = false; - // Set the DH private key for crypto operations LOG_DEBUG("Set DH private key for crypto operations"); crypto->setDHPrivateKey(config.security.private_key.bytes); @@ -2847,6 +2847,10 @@ bool NodeDB::createNewIdentity() memset(node->public_key.bytes, 0, sizeof(node->public_key.bytes)); } + // Drop satellite-store entries (position/telemetry/environment/status) keyed by the retired + // node number so stale data isn't left attached to the old identity. + eraseNodeSatellites(oldNodeNum); + myNodeInfo.my_node_num = newNodeNum; meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index b2f4f750c..5e13d8a6a 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -495,7 +495,7 @@ extern uint32_t error_address; #define NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK (1u << NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_SHIFT) #define NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_SHIFT 9 #define NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK (1u << NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_SHIFT) -// Bits 9..31 reserved for future single-bit flags. +// Bits 10..31 reserved for future single-bit flags. // Convenience accessors so call sites read like the old struct fields. inline bool nodeInfoLiteHasUser(const meshtastic_NodeInfoLite *n) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a087a7493..c9ae9fc86 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -548,10 +548,14 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p) LOG_DEBUG("No public key for 0x%08x, cannot verify XEdDSA signature", p->from); } } else { - // Unsigned packet — reject if this node previously sent signed packets + // Unsigned packet — only reject the class of packet a signing node always signs: + // an unencrypted broadcast small enough to also carry a signature (see perhapsEncode()). + // Unicast packets and oversized broadcasts are never signed, so they must not be + // hard-failed here even if this node has signed before. meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->from); - if (node && nodeInfoLiteHasXeddsaSigned(node)) { - LOG_WARN("Dropping unsigned packet from 0x%08x that previously signed", p->from); + if (node && nodeInfoLiteHasXeddsaSigned(node) && isBroadcast(p->to) && + p->decoded.payload.size + XEDDSA_SIGNATURE_SIZE < meshtastic_Constants_DATA_PAYLOAD_LEN) { + LOG_WARN("Dropping unsigned broadcast from 0x%08x that previously signed", p->from); return DecodeState::DECODE_FAILURE; } }