From c5c634ee27d6fc41b0825cc49fbd4622b25e7a93 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 14 Aug 2025 21:06:55 -0500 Subject: [PATCH] Generate a new node identity on key generation (#7628) * Generate a new node identity on key generation * Fixes * Fixes * Fixes * Messed up * Fixes * Update src/modules/AdminModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/mesh/NodeDB.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Figured it out! * Cleanup * Update src/mesh/NodeDB.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/mesh/NodeDB.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/AdminModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/graphics/draw/MenuHandler.cpp | 21 +----- src/mesh/NodeDB.cpp | 115 ++++++++++++++++++++++++------ src/mesh/NodeDB.h | 6 ++ src/modules/AdminModule.cpp | 50 ++++--------- 4 files changed, 115 insertions(+), 77 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index b7bd068c4..f8e7bce35 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -67,25 +67,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration) if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) { config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected); // This is needed as we wait til picking the LoRa region to generate keys for the first time. - if (!owner.is_licensed) { - bool keygenSuccess = false; - if (config.security.private_key.size == 32) { - // public key is derived from private, so this will always have the same result. - if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { - keygenSuccess = true; - } - } else { - LOG_INFO("Generate new PKI keys"); - 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); - } - } + // Use consolidated key generation function + nodeDB->generateCryptoKeyPair(); config.lora.tx_enabled = true; initRegion(); if (myRegion->dutyCycle < 100) { diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 79361bb46..836bec086 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -242,8 +242,6 @@ 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 @@ -261,31 +259,18 @@ NodeDB::NodeDB() } #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) - - 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); - } - } + // 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); #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 @@ -1876,6 +1861,94 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub return false; } +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() +{ + // Remove the old node from the NodeDB + uint32_t oldNodeNum = getNodeNum(); + meshtastic_NodeInfoLite *node = getMeshNode(oldNodeNum); + + // 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); + + if (node != NULL && myNodeInfo.my_node_num != oldNodeNum) { + LOG_DEBUG("Old node num %u is now %u", oldNodeNum, myNodeInfo.my_node_num); + node->is_ignored = true; + node->has_device_metrics = false; + node->has_position = false; + node->user.public_key.size = 0; + node->user.public_key.bytes[0] = 0; + } + + meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); + info->user = TypeConversions::ConvertToUserLite(owner); + info->has_user = true; + + return true; +} + bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location) { bool success = false; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index d608b044f..c12bf8513 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -270,6 +270,12 @@ class NodeDB bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest); + /// 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); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 4014e1c36..068c7f9c0 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -729,24 +729,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.lora = c.payload_variant.lora; // If we're setting region for the first time, init the region and regenerate the keys if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { - if (!owner.is_licensed) { - bool keygenSuccess = false; - if (config.security.private_key.size == 32) { - if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { - keygenSuccess = true; - } - } else { - LOG_INFO("Generate new PKI keys"); - 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); - } - } + // Use consolidated key generation function + nodeDB->generateCryptoKeyPair(); config.lora.tx_enabled = true; initRegion(); if (myRegion->dutyCycle < 100) { @@ -754,7 +738,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) } if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) { sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); - changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; + changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_NODEDATABASE | SEGMENT_DEVICESTATE; } } break; @@ -767,22 +751,14 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) LOG_INFO("Set config: Security"); config.security = c.payload_variant.security; #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) && !(MESHTASTIC_EXCLUDE_PKI) - // 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; - } - } + // 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); } -#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)) { @@ -792,9 +768,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) sendWarning(warning); } - 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; + changes = SEGMENT_CONFIG | SEGMENT_DEVICESTATE | SEGMENT_NODEDATABASE; + + requiresReboot = true; break; case meshtastic_Config_device_ui_tag: