feat(security): enforce packet authenticity policies

This commit is contained in:
Benjamin Faershtein
2026-07-09 17:48:55 -07:00
parent b3ddeca9d0
commit d6b12ea3f1
19 changed files with 1136 additions and 169 deletions
+59
View File
@@ -334,6 +334,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{
@@ -596,6 +597,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;
@@ -687,6 +690,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();
@@ -698,6 +703,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);
@@ -729,6 +736,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;
@@ -744,6 +753,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.
@@ -1063,6 +1119,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);