Merge pull request #10967 from RCGV1/codex/packet-auth-policy

This commit is contained in:
Ben Meadors
2026-07-21 11:32:40 -05:00
committed by GitHub
co-authored by GitHub
19 changed files with 1183 additions and 157 deletions
+2 -1
View File
@@ -159,7 +159,8 @@ void test_E1_perhaps_decode_fuzz(void)
DecodeState st = perhapsDecode(&p);
// Any verdict is fine; the contract is that arbitrary ciphertext never crashes the pipeline.
TEST_ASSERT_TRUE(st == DECODE_SUCCESS || st == DECODE_FAILURE || st == DECODE_FATAL);
TEST_ASSERT_TRUE(st == DECODE_SUCCESS || st == DECODE_FAILURE || st == DECODE_OPAQUE || st == DECODE_FATAL ||
st == DECODE_POLICY_REJECT);
}
}
+68
View File
@@ -53,6 +53,15 @@ class MockRouter : public Router
class MockMeshService : public MeshService
{
public:
// No PhoneAPI reader exists in these tests, so packets the receive pipeline forwards to the phone
// (MeshService::sendToPhone enqueues pooled copies into toPhoneQueue) would leak at teardown. Drain
// the queue like the phone would. This surfaced once sendLocal() began dispatching local packets
// through handleReceived() directly rather than via the (mock-overridden) enqueueReceivedMessage().
~MockMeshService()
{
while (meshtastic_MeshPacket *p = getForPhone())
releaseToPool(p);
}
void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m) override
{
messages_.emplace_back(*m);
@@ -334,6 +343,7 @@ const meshtastic_MeshPacket encrypted = {
// Initialize mocks and configuration before running each test.
void setUp(void)
{
config = meshtastic_LocalConfig_init_zero;
moduleConfig.mqtt =
meshtastic_ModuleConfig_MQTTConfig{.enabled = true, .map_reporting_enabled = true, .has_map_report_settings = true};
moduleConfig.mqtt.map_report_settings = meshtastic_ModuleConfig_MapReportSettings{
@@ -627,6 +637,8 @@ void test_receiveWithoutChannelDownlink(void)
// Test receiving an encrypted MeshPacket on the PKI topic.
void test_receiveEncryptedPKITopicToUs(void)
{
config.security.packet_signature_policy =
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_STRICT;
meshtastic_MeshPacket e = encrypted;
e.to = myNodeInfo.my_node_num;
@@ -718,6 +730,8 @@ static meshtastic_MeshPacket makeDecodedBroadcast()
// signing node (audit F3).
void test_receiveDropsUnsignedBroadcastFromSigner(void)
{
config.security.packet_signature_policy =
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_BALANCED;
mockNodeDB->emptyNode.bitfield |= NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK;
const meshtastic_MeshPacket p = makeDecodedBroadcast();
@@ -729,6 +743,8 @@ void test_receiveDropsUnsignedBroadcastFromSigner(void)
// The same unsigned broadcast from a node never seen signing is accepted.
void test_receiveAcceptsUnsignedBroadcastFromNonSigner(void)
{
config.security.packet_signature_policy =
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_BALANCED;
const meshtastic_MeshPacket p = makeDecodedBroadcast();
unitTest->publish(&p);
@@ -760,6 +776,8 @@ void test_receiveVerifiesSignedDecodedDownlink(void)
// A decoded downlink carrying a signature that fails verification is dropped.
void test_receiveDropsBadSignatureOnDecodedDownlink(void)
{
config.security.packet_signature_policy =
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_COMPATIBLE;
uint8_t pub[32], priv[32];
crypto->generateKeyPair(pub, priv);
mockNodeDB->emptyNode.public_key.size = 32;
@@ -775,6 +793,53 @@ void test_receiveDropsBadSignatureOnDecodedDownlink(void)
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
}
void test_receiveCompatibleAcceptsUnsignedBroadcastFromSigner(void)
{
config.security.packet_signature_policy =
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_COMPATIBLE;
mockNodeDB->emptyNode.bitfield |= NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK;
const meshtastic_MeshPacket p = makeDecodedBroadcast();
unitTest->publish(&p);
TEST_ASSERT_EQUAL(1, mockRouter->packets_.size());
}
void test_receiveStrictDropsUnsignedPortnumsAndUnicast(void)
{
config.security.packet_signature_policy =
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_STRICT;
const meshtastic_PortNum ports[] = {
meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_POSITION_APP, meshtastic_PortNum_TELEMETRY_APP,
meshtastic_PortNum_NODEINFO_APP, meshtastic_PortNum_WAYPOINT_APP,
};
for (const auto port : ports) {
meshtastic_MeshPacket p = makeDecodedBroadcast();
p.decoded.portnum = port;
unitTest->publish(&p);
}
meshtastic_MeshPacket unicast = makeDecodedBroadcast();
unicast.to = myNodeInfo.my_node_num;
unicast.decoded.portnum = meshtastic_PortNum_POSITION_APP;
unitTest->publish(&unicast);
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
}
// A plaintext broker assertion is not evidence that AES-CCM authentication succeeded locally.
void test_receiveStrictDoesNotTrustDecodedPkiFlag(void)
{
config.security.packet_signature_policy =
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_STRICT;
meshtastic_MeshPacket p = makeDecodedBroadcast();
p.to = myNodeInfo.my_node_num;
p.pki_encrypted = true;
unitTest->publish(&p);
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
}
#endif // !(MESHTASTIC_EXCLUDE_PKI) && !(MESHTASTIC_EXCLUDE_XEDDSA)
// Only the same fields that are transmitted over LoRa should be set in MQTT messages.
@@ -1096,6 +1161,9 @@ void setup()
RUN_TEST(test_receiveAcceptsUnsignedBroadcastFromNonSigner);
RUN_TEST(test_receiveVerifiesSignedDecodedDownlink);
RUN_TEST(test_receiveDropsBadSignatureOnDecodedDownlink);
RUN_TEST(test_receiveCompatibleAcceptsUnsignedBroadcastFromSigner);
RUN_TEST(test_receiveStrictDropsUnsignedPortnumsAndUnicast);
RUN_TEST(test_receiveStrictDoesNotTrustDecodedPkiFlag);
#endif
RUN_TEST(test_receiveIgnoresUnexpectedFields);
RUN_TEST(test_receiveIgnoresInvalidHopLimit);
File diff suppressed because it is too large Load Diff