Compare commits

..
20 changed files with 120 additions and 331 deletions
+3 -95
View File
@@ -6,21 +6,16 @@
#if !(MESHTASTIC_EXCLUDE_PKI)
#include "HardwareRNG.h"
#include "NodeDB.h"
#include "XEdDSA.h"
#include "aes-ccm.h"
#include "meshUtils.h"
#include <Crypto.h>
#include <Curve25519.h>
#include <Ed25519.h>
#include <RNG.h>
#include <SHA256.h>
#ifndef NUM_LIMBS_256BIT
#define NUM_LIMBS_BITS(n) (((n) + sizeof(limb_t) * 8 - 1) / (8 * sizeof(limb_t)))
#define NUM_LIMBS_256BIT NUM_LIMBS_BITS(256)
#endif
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
#if !defined(ARCH_STM32WL)
#define CryptRNG RNG
#endif
/**
* Create a public/private key pair with Curve25519.
@@ -51,7 +46,6 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
Curve25519::dh1(public_key, private_key);
memcpy(pubKey, public_key, sizeof(public_key));
memcpy(privKey, private_key, sizeof(private_key));
XEdDSA::priv_curve_to_ed_keys(private_key, xeddsa_private_key, xeddsa_public_key);
}
/**
@@ -71,7 +65,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
}
memcpy(private_key, privKey, sizeof(private_key));
memcpy(public_key, pubKey, sizeof(public_key));
XEdDSA::priv_curve_to_ed_keys(private_key, xeddsa_private_key, xeddsa_public_key);
} else {
LOG_WARN("X25519 key generation failed due to blank private key");
return false;
@@ -79,91 +72,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
return true;
}
/**
* Build a signing buffer that covers packet metadata and payload:
* [fromNode(4) | packetId(4) | portnum(4) | payload(N)]
* This prevents replay, reattribution, and portnum redirection attacks.
*/
static size_t buildSigningBuffer(uint8_t *buf, size_t bufSize, uint32_t fromNode, uint32_t packetId, uint32_t portnum,
const uint8_t *payload, size_t payloadLen)
{
const size_t headerLen = sizeof(uint32_t) * 3;
size_t totalLen = headerLen + payloadLen;
if (totalLen > bufSize)
return 0;
memcpy(buf, &fromNode, sizeof(uint32_t));
memcpy(buf + sizeof(uint32_t), &packetId, sizeof(uint32_t));
memcpy(buf + sizeof(uint32_t) * 2, &portnum, sizeof(uint32_t));
memcpy(buf + headerLen, payload, payloadLen);
return totalLen;
}
bool CryptoEngine::xeddsa_sign(uint32_t fromNode, uint32_t packetId, uint32_t portnum, const uint8_t *payload, size_t payloadLen,
uint8_t *signature)
{
if (memfll(xeddsa_private_key, 0, sizeof(xeddsa_private_key)))
return false;
uint8_t sigBuf[MAX_BLOCKSIZE];
size_t sigLen = buildSigningBuffer(sigBuf, sizeof(sigBuf), fromNode, packetId, portnum, payload, payloadLen);
if (sigLen == 0)
return false;
XEdDSA::sign(signature, xeddsa_private_key, xeddsa_public_key, sigBuf, sigLen);
return true;
}
bool CryptoEngine::xeddsa_verify(const uint8_t *pubKey, uint32_t fromNode, uint32_t packetId, uint32_t portnum,
const uint8_t *payload, size_t payloadLen, const uint8_t *signature)
{
// Use cached Ed25519 key if the Curve25519 key matches, avoiding expensive field inversion
if (memcmp(pubKey, cached_curve_pubkey, 32) != 0) {
curve_to_ed_pub(pubKey, cached_ed_pubkey);
memcpy(cached_curve_pubkey, pubKey, 32);
}
uint8_t sigBuf[MAX_BLOCKSIZE];
size_t sigLen = buildSigningBuffer(sigBuf, sizeof(sigBuf), fromNode, packetId, portnum, payload, payloadLen);
if (sigLen == 0)
return false;
return XEdDSA::verify(signature, cached_ed_pubkey, sigBuf, sigLen);
}
void CryptoEngine::curve_to_ed_pub(const uint8_t *curve_pubkey, uint8_t *ed_pubkey)
{
// Apply the birational map defined in RFC 7748, section 4.1 "Curve25519" to calculate an Ed25519 public
// 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.
fe u, y;
fe one;
fe u_minus_one, u_plus_one, u_plus_one_inv;
// Parse the Curve25519 public key input as a field element containing the u coordinate. RFC 7748,
// section 5 "The X25519 and X448 Functions", mandates that the most significant bit of the Curve25519
// public key has to be zeroized. This is handled by fe_frombytes internally.
fe_frombytes(u, curve_pubkey);
// Calculate the parameters (u - 1) and (u + 1)
fe_1(one);
fe_sub(u_minus_one, u, one);
fe_add(u_plus_one, u, one);
// Invert u + 1
fe_invert(u_plus_one_inv, u_plus_one);
// Calculate y = (u - 1) * inv(u + 1) (mod p)
fe_mul(y, u_minus_one, u_plus_one_inv);
// Serialize the field element containing the y coordinate to the Ed25519 public key output
fe_tobytes(ed_pubkey, y);
// Set the sign bit to zero
ed_pubkey[31] &= 0x7f;
// need to convert the pubkey y = ( u - 1) * inv( u + 1) (mod p).
}
bool CryptoEngine::ensurePkiKeys(meshtastic_Config_SecurityConfig &security, meshtastic_User &user)
{
if (user.is_licensed) {
-11
View File
@@ -23,7 +23,6 @@ struct CryptoKey {
#define MAX_BLOCKSIZE 256
#define TEST_CURVE25519_FIELD_OPS // Exposes Curve25519::isWeakPoint() for testing keys
#define XEDDSA_SIGNATURE_SIZE 64
class CryptoEngine
{
@@ -38,10 +37,6 @@ class CryptoEngine
virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey);
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
virtual bool ensurePkiKeys(meshtastic_Config_SecurityConfig &security, meshtastic_User &user);
bool xeddsa_sign(uint32_t fromNode, uint32_t packetId, uint32_t portnum, const uint8_t *payload, size_t payloadLen,
uint8_t *signature);
bool xeddsa_verify(const uint8_t *pubKey, uint32_t fromNode, uint32_t packetId, uint32_t portnum, const uint8_t *payload,
size_t payloadLen, const uint8_t *signature);
#endif
void setDHPrivateKey(uint8_t *_private_key);
@@ -90,12 +85,6 @@ class CryptoEngine
#if !(MESHTASTIC_EXCLUDE_PKI)
uint8_t shared_key[32] = {0};
uint8_t private_key[32] = {0};
uint8_t xeddsa_public_key[32] = {0};
uint8_t xeddsa_private_key[32] = {0};
void curve_to_ed_pub(const uint8_t *curve_pubkey, uint8_t *ed_pubkey);
// Single-entry cache for curve_to_ed_pub conversion (avoids expensive field inversion per packet)
uint8_t cached_curve_pubkey[32] = {0};
uint8_t cached_ed_pubkey[32] = {0};
#endif
/**
* Init our 128 bit nonce for a new packet
+21 -95
View File
@@ -431,6 +431,8 @@ NodeDB::NodeDB()
// likewise - we always want the app requirements to come from the running appload
myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
pickNewNodeNum();
// Set our board type so we can share it with others
@@ -450,18 +452,31 @@ NodeDB::NodeDB()
}
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
// Generate crypto keys if needed using consolidated function
// Set my node num uint32 value to bytes from the public key (if we have one)
// Generate identity and crypto keys if needed; this will create a new identity if one does not exist
generateCryptoKeyPair(nullptr);
if (!owner.is_licensed && config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
bool keygenSuccess = false;
keyIsLowEntropy = checkLowEntropyPublicKey(config.security.public_key);
if (config.security.private_key.size == 32 && !keyIsLowEntropy) {
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
keygenSuccess = true;
}
if (keygenSuccess) {
config.security.public_key.size = 32;
config.security.private_key.size = 32;
owner.public_key.size = 32;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
}
#elif !(MESHTASTIC_EXCLUDE_PKI)
// Calculate Curve25519 public and private keys
if (config.security.private_key.size == 32 && config.security.public_key.size == 32) {
owner.public_key.size = config.security.public_key.size;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size);
crypto->setDHPrivateKey(config.security.private_key.bytes);
// Set my node num uint32 value to bytes from the new public key
myNodeInfo.my_node_num = crc32Buffer(config.security.public_key.bytes, config.security.public_key.size);
}
#endif
// Include our owner in the node db under our nodenum
@@ -2766,95 +2781,6 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
}
#endif
bool NodeDB::generateCryptoKeyPair(const uint8_t *privateKey)
{
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
// Only generate keys for non-licensed users and if LoRa region is set
if (owner.is_licensed || config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
return false;
}
bool keygenSuccess = false;
bool lowEntropy = checkLowEntropyPublicKey(config.security.public_key);
// If a specific private key was provided, use it
if (privateKey != nullptr) {
LOG_INFO("Using provided private key for PKI");
memcpy(config.security.private_key.bytes, privateKey, 32);
config.security.private_key.size = 32;
config.security.public_key.size = 32;
// Generate public key from the provided private key
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
} else {
LOG_ERROR("Failed to generate public key from provided private key");
return false;
}
}
// 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) {
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)) {
keygenSuccess = true;
}
} else {
// Generate a new key pair
LOG_INFO("Generate new PKI keys");
config.security.public_key.size = 32;
config.security.private_key.size = 32;
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
keygenSuccess = true;
}
// Update sizes and copy to owner if successful
if (keygenSuccess) {
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);
// Conditionally create new identity based on parameter
createNewIdentity();
}
return keygenSuccess;
#else
return false;
#endif
}
bool NodeDB::createNewIdentity()
{
uint32_t oldNodeNum = getNodeNum();
uint32_t newNodeNum = crc32Buffer(config.security.public_key.bytes, config.security.public_key.size);
// If the key hasn't changed, nothing to do
if (newNodeNum == oldNodeNum)
return false;
// Retire the old node entry
meshtastic_NodeInfoLite *node = getMeshNode(oldNodeNum);
if (node != NULL) {
LOG_DEBUG("Old node num %u is now %u", oldNodeNum, newNodeNum);
nodeInfoLiteSetBit(node, NODEINFO_BITFIELD_IS_IGNORED_MASK, true);
node->public_key.size = 0;
memset(node->public_key.bytes, 0, sizeof(node->public_key.bytes));
}
myNodeInfo.my_node_num = newNodeNum;
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
TypeConversions::CopyUserToNodeInfoLite(info, owner);
return true;
}
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
{
bool success = false;
-12
View File
@@ -376,12 +376,6 @@ class NodeDB
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
#endif
/// Consolidate crypto key generation logic used across multiple modules
/// @param privateKey Optional 32-byte private key to use. If nullptr, generates new random keys.
bool generateCryptoKeyPair(const uint8_t *privateKey = nullptr);
bool createNewIdentity();
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
@@ -493,8 +487,6 @@ extern uint32_t error_address;
#define NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK (1u << NODEINFO_BITFIELD_IS_UNMESSAGABLE_SHIFT)
#define NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_SHIFT 8
#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.
// Convenience accessors so call sites read like the old struct fields.
@@ -534,10 +526,6 @@ inline bool nodeInfoLiteIsKeyManuallyVerified(const meshtastic_NodeInfoLite *n)
{
return n && (n->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK);
}
inline bool nodeInfoLiteHasXeddsaSigned(const meshtastic_NodeInfoLite *n)
{
return n && (n->bitfield & NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK);
}
inline void nodeInfoLiteSetBit(meshtastic_NodeInfoLite *n, uint32_t mask, bool value)
{
+9 -7
View File
@@ -4,17 +4,19 @@
#include <Arduino.h>
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
uint32_t getPositionPrecisionForChannel(const meshtastic_Channel &channel)
{
const meshtastic_Channel &channel = channels.getByIndex(channelIndex);
if (channel.settings.has_module_settings) {
return channel.settings.module_settings.position_precision;
} else if (channel.role == meshtastic_Channel_Role_PRIMARY) {
return 32;
} else {
return 0;
}
// No module settings: fail closed. A PRIMARY channel used to default to 32
// here, leaking an exact position on a sharing-disabled channel. See #10509.
return 0;
}
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
{
return getPositionPrecisionForChannel(channels.getByIndex(channelIndex));
}
static int32_t truncateCoordinate(int32_t coordinate, uint32_t precision)
+2
View File
@@ -1,8 +1,10 @@
#pragma once
#include "meshtastic/channel.pb.h"
#include "meshtastic/mesh.pb.h"
#include <stdint.h>
uint32_t getPositionPrecisionForChannel(const meshtastic_Channel &channel);
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex);
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision);
bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision);
-36
View File
@@ -529,32 +529,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
if (p->decoded.has_bitfield)
p->decoded.want_response |= p->decoded.bitfield & BITFIELD_WANT_RESPONSE_MASK;
if (p->decoded.xeddsa_signature.size == XEDDSA_SIGNATURE_SIZE) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->from);
if (node && node->public_key.size == 32) {
p->xeddsa_signed =
crypto->xeddsa_verify(node->public_key.bytes, p->from, p->id, p->decoded.portnum, p->decoded.payload.bytes,
p->decoded.payload.size, p->decoded.xeddsa_signature.bytes);
if (p->xeddsa_signed) {
// Mark this node as a signer so future unsigned packets from it are rejected
nodeInfoLiteSetBit(node, NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK, true);
LOG_DEBUG("Verified XEdDSA signature from 0x%08x", p->from);
} else {
LOG_WARN("XEdDSA signature verification failed from 0x%08x, dropping", p->from);
return DecodeState::DECODE_FAILURE;
}
} else {
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
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->from);
if (node && nodeInfoLiteHasXeddsaSigned(node)) {
LOG_WARN("Dropping unsigned packet from 0x%08x that previously signed", p->from);
return DecodeState::DECODE_FAILURE;
}
}
/* Not actually ever used.
// Decompress if needed. jm
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) {
@@ -627,16 +601,6 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
p->decoded.has_bitfield = true;
p->decoded.bitfield |= (config.lora.config_ok_to_mqtt << BITFIELD_OK_TO_MQTT_SHIFT);
p->decoded.bitfield |= (p->decoded.want_response << BITFIELD_WANT_RESPONSE_SHIFT);
// Sign broadcast packets if payload + signature fits within the max Data payload.
// The actual encoded size is checked after pb_encode (TOO_LARGE).
if (!p->pki_encrypted && isBroadcast(p->to) &&
p->decoded.payload.size + XEDDSA_SIGNATURE_SIZE < meshtastic_Constants_DATA_PAYLOAD_LEN) {
if (crypto->xeddsa_sign(p->from, p->id, p->decoded.portnum, p->decoded.payload.bytes, p->decoded.payload.size,
p->decoded.xeddsa_signature.bytes)) {
p->decoded.xeddsa_signature.size = XEDDSA_SIGNATURE_SIZE;
LOG_DEBUG("XEdDSA signed packet 0x%08x", p->id);
}
}
}
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
-1
View File
@@ -18,7 +18,6 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
info.is_ignored = nodeInfoLiteIsIgnored(lite);
info.is_key_manually_verified = nodeInfoLiteIsKeyManuallyVerified(lite);
info.is_muted = nodeInfoLiteIsMuted(lite);
info.has_xeddsa_signed = nodeInfoLiteHasXeddsaSigned(lite);
if (lite->has_hops_away) {
info.has_hops_away = true;
+18 -10
View File
@@ -895,14 +895,22 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
LOG_INFO("Set config: Security");
config.security = c.payload_variant.security;
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) && !(MESHTASTIC_EXCLUDE_PKI)
// Only regenerate keys if the private key is not 32 bytes
if (config.security.private_key.size != 32) {
nodeDB->generateCryptoKeyPair();
}
// If user provided a private key of correct size but no public key, generate the public key from private key
else if (config.security.private_key.size == 32 && config.security.public_key.size == 0) {
nodeDB->generateCryptoKeyPair(config.security.private_key.bytes);
// If the client set the key to blank, go ahead and regenerate so long as we're not in ham mode
if (!owner.is_licensed && config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
if (config.security.private_key.size != 32) {
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
} else {
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
config.security.public_key.size = 32;
}
}
}
#endif
owner.public_key.size = config.security.public_key.size;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size);
#if !MESHTASTIC_EXCLUDE_PKI
crypto->setDHPrivateKey(config.security.private_key.bytes);
#endif
if (config.security.is_managed && !(config.security.admin_key[0].size == 32 || config.security.admin_key[1].size == 32 ||
config.security.admin_key[2].size == 32)) {
@@ -912,9 +920,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
sendWarning(warning);
}
changes = SEGMENT_CONFIG | SEGMENT_DEVICESTATE | SEGMENT_NODEDATABASE;
requiresReboot = true;
if (config.security.debug_log_api_enabled == c.payload_variant.security.debug_log_api_enabled &&
config.security.serial_enabled == c.payload_variant.security.serial_enabled)
requiresReboot = false;
break;
case meshtastic_Config_device_ui_tag:
-6
View File
@@ -49,12 +49,6 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
}
NodeNum sourceNum = getFrom(&mp);
auto node = nodeDB->getMeshNode(sourceNum);
if (node && nodeInfoLiteHasXeddsaSigned(node) && !mp.xeddsa_signed) {
LOG_WARN("Dropping unsigned NodeInfo from node 0x%08x that previously signed", sourceNum);
return true;
}
// Coerce user.id to be derived from the node number
snprintf(p.id, sizeof(p.id), "!%08x", getFrom(&mp));
-44
View File
@@ -2,7 +2,6 @@
#include "CryptoEngine.h"
#include "TestUtil.h"
#include <XEdDSA.h>
#include <unity.h>
void HexToBytes(uint8_t *result, const std::string hex, size_t len = 0)
@@ -153,48 +152,6 @@ void test_PKC(void)
TEST_ASSERT_EQUAL_MEMORY(expected_decrypted, decrypted, 10);
}
void test_XEdDSA(void)
{
uint8_t private_key[32];
uint8_t x_public_key[32];
uint8_t ed_private_key[32];
uint8_t ed_public_key[32];
uint8_t ed_public_key2[32];
uint8_t message[] = "This is a test!";
uint8_t message2[] = "This is a test.";
uint8_t signature[64];
uint32_t fromNode = 0x1234;
uint32_t packetId = 0xDEADBEEF;
uint32_t portnum = 1;
for (int times = 0; times < 10; times++) {
printf("Start of time %u\n", times);
crypto->generateKeyPair(x_public_key, private_key);
XEdDSA::priv_curve_to_ed_keys(private_key, ed_private_key, ed_public_key);
crypto->curve_to_ed_pub(x_public_key, ed_public_key2);
TEST_ASSERT_EQUAL_MEMORY(ed_public_key, ed_public_key2, 32);
// Sign and verify with metadata
TEST_ASSERT(crypto->xeddsa_sign(fromNode, packetId, portnum, message, sizeof(message), signature));
TEST_ASSERT(crypto->xeddsa_verify(x_public_key, fromNode, packetId, portnum, message, sizeof(message), signature));
// Different payload fails
TEST_ASSERT_FALSE(
crypto->xeddsa_verify(x_public_key, fromNode, packetId, portnum, message2, sizeof(message2), signature));
// Different fromNode fails
TEST_ASSERT_FALSE(
crypto->xeddsa_verify(x_public_key, fromNode + 1, packetId, portnum, message, sizeof(message), signature));
// Different packetId fails
TEST_ASSERT_FALSE(
crypto->xeddsa_verify(x_public_key, fromNode, packetId + 1, portnum, message, sizeof(message), signature));
// Different portnum fails
TEST_ASSERT_FALSE(
crypto->xeddsa_verify(x_public_key, fromNode, packetId, portnum + 1, message, sizeof(message), signature));
}
}
void test_AES_CTR(void)
{
uint8_t expected[32];
@@ -235,7 +192,6 @@ void setup()
RUN_TEST(test_DH25519);
RUN_TEST(test_AES_CTR);
RUN_TEST(test_PKC);
RUN_TEST(test_XEdDSA);
exit(UNITY_END()); // stop unit testing
}
@@ -19,6 +19,16 @@ static meshtastic_Position makePosition()
return position;
}
static meshtastic_Channel makeChannel(meshtastic_Channel_Role role, bool hasModuleSettings, uint32_t positionPrecision)
{
meshtastic_Channel channel = meshtastic_Channel_init_default;
channel.has_settings = true;
channel.role = role;
channel.settings.has_module_settings = hasModuleSettings;
channel.settings.module_settings.position_precision = positionPrecision;
return channel;
}
static void test_applyPositionPrecision_clampsLatLonAndSetsPrecisionBits()
{
meshtastic_Position position = makePosition();
@@ -80,6 +90,35 @@ static void test_applyPositionPrecision_reencodesPositionPacket()
TEST_ASSERT_EQUAL_UINT32(16, decoded.precision_bits);
}
static void test_getPositionPrecisionForChannel_explicitPrecisionIsHonored()
{
meshtastic_Channel channel = makeChannel(meshtastic_Channel_Role_PRIMARY, true, 16);
TEST_ASSERT_EQUAL_UINT32(16, getPositionPrecisionForChannel(channel));
}
static void test_getPositionPrecisionForChannel_explicitZeroDisablesPrimary()
{
meshtastic_Channel channel = makeChannel(meshtastic_Channel_Role_PRIMARY, true, 0);
TEST_ASSERT_EQUAL_UINT32(0, getPositionPrecisionForChannel(channel));
}
static void test_getPositionPrecisionForChannel_primaryWithoutModuleSettingsFailsClosed()
{
// Regression guard for #10509: precision 32 below must be ignored (no module settings).
meshtastic_Channel channel = makeChannel(meshtastic_Channel_Role_PRIMARY, false, 32);
TEST_ASSERT_EQUAL_UINT32(0, getPositionPrecisionForChannel(channel));
}
static void test_getPositionPrecisionForChannel_secondaryWithoutModuleSettingsFailsClosed()
{
meshtastic_Channel channel = makeChannel(meshtastic_Channel_Role_SECONDARY, false, 32);
TEST_ASSERT_EQUAL_UINT32(0, getPositionPrecisionForChannel(channel));
}
void setUp(void) {}
void tearDown(void) {}
@@ -93,6 +132,10 @@ void setup()
RUN_TEST(test_applyPositionPrecision_fullPrecisionKeepsLatLon);
RUN_TEST(test_applyPositionPrecision_zeroScrubsLocationButKeepsTime);
RUN_TEST(test_applyPositionPrecision_reencodesPositionPacket);
RUN_TEST(test_getPositionPrecisionForChannel_explicitPrecisionIsHonored);
RUN_TEST(test_getPositionPrecisionForChannel_explicitZeroDisablesPrimary);
RUN_TEST(test_getPositionPrecisionForChannel_primaryWithoutModuleSettingsFailsClosed);
RUN_TEST(test_getPositionPrecisionForChannel_secondaryWithoutModuleSettingsFailsClosed);
exit(UNITY_END());
}
+2 -2
View File
@@ -70,8 +70,8 @@ lib_deps =
https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.3
# renovate: datasource=git-refs depName=meshtastic/Crypto packageName=https://github.com/meshtastic/Crypto gitBranch=master
https://github.com/meshtastic/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
lib_ignore =
segger_rtt
@@ -11,6 +11,11 @@ build_flags =
-I variants/esp32s3/ELECROW-ThinkNode-G3
-mfix-esp32-psram-cache-issue
custom_sdkconfig =
${esp32s3_base.custom_sdkconfig}
CONFIG_ETH_ENABLED=y
CONFIG_ARDUINO_SELECTIVE_Ethernet=y
lib_ignore =
Ethernet
@@ -2,7 +2,7 @@
extends = esp32s3_base
board = ThinkNode-M7
build_flags =
build_flags =
${esp32s3_base.build_flags}
-D ELECROW_ThinkNode_M7
-D HAS_UDP_MULTICAST=1
@@ -10,7 +10,12 @@ build_flags =
-I variants/esp32s3/ELECROW-ThinkNode-M7
-mfix-esp32-psram-cache-issue
lib_ignore =
custom_sdkconfig =
${esp32s3_base.custom_sdkconfig}
CONFIG_ETH_ENABLED=y
CONFIG_ARDUINO_SELECTIVE_Ethernet=y
lib_ignore =
Ethernet
build_src_filter =
+2 -2
View File
@@ -25,8 +25,8 @@ lib_deps =
${networking_extra.lib_deps}
${radiolib_base.lib_deps}
${environmental_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic/Crypto packageName=https://github.com/meshtastic/Crypto
https://github.com/meshtastic/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.21
; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
+2 -2
View File
@@ -45,8 +45,8 @@ build_src_filter =
lib_deps=
${arduino_base.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=github-tags depName=meshtastic/Crypto packageName=meshtastic/Crypto
https://github.com/meshtastic/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
lib_ignore =
BluetoothOTA
+2 -2
View File
@@ -31,5 +31,5 @@ lib_deps =
${environmental_base.lib_deps}
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=github-tags depName=meshtastic/Crypto packageName=meshtastic/Crypto
https://github.com/meshtastic/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
+2 -2
View File
@@ -28,5 +28,5 @@ lib_deps =
${environmental_base.lib_deps}
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=github-tags depName=meshtastic/Crypto packageName=meshtastic/Crypto
https://github.com/meshtastic/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
+2 -2
View File
@@ -53,8 +53,8 @@ debug_tool = stlink
lib_deps =
${env.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic/Crypto packageName=https://github.com/meshtastic/Crypto gitBranch=main
https://github.com/meshtastic/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
https://github.com/caveman99/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
lib_ignore =
OneButton