Key regen and MQTT fix (#4585)

* Add public key regen

* Properly label and handle PKI MQTT packets

* Extra debug message to indicate PKI_UNKNOWN_PUBKEY

* Ternary!

* Don't call non-existant function on stm32

* Actually fix STM32 compilation
This commit is contained in:
Jonathan Bennett 2024-08-29 16:28:03 -05:00 committed by GitHub
parent 22454c95c7
commit 5bc17a9911
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 62 additions and 25 deletions

View File

@ -11,6 +11,7 @@
#include <Curve25519.h> #include <Curve25519.h>
#include <SHA256.h> #include <SHA256.h>
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
/** /**
* Create a public/private key pair with Curve25519. * Create a public/private key pair with Curve25519.
* *
@ -24,6 +25,30 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
memcpy(pubKey, public_key, sizeof(public_key)); memcpy(pubKey, public_key, sizeof(public_key));
memcpy(privKey, private_key, sizeof(private_key)); memcpy(privKey, private_key, sizeof(private_key));
} }
/**
* regenerate a public key with Curve25519.
*
* @param pubKey The destination for the public key.
* @param privKey The source for the private key.
*/
bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
{
if (!memfll(privKey, 0, sizeof(private_key))) {
Curve25519::eval(pubKey, privKey, 0);
if (Curve25519::isWeakPoint(pubKey)) {
LOG_ERROR("PKI key generation failed. Specified private key results in a weak\n");
memset(pubKey, 0, 32);
return false;
}
memcpy(private_key, privKey, sizeof(private_key));
memcpy(public_key, pubKey, sizeof(public_key));
} else {
LOG_WARN("X25519 key generation failed due to blank private key\n");
return false;
}
return true;
}
#endif #endif
void CryptoEngine::clearKeys() void CryptoEngine::clearKeys()
{ {

View File

@ -21,6 +21,7 @@ struct CryptoKey {
*/ */
#define MAX_BLOCKSIZE 256 #define MAX_BLOCKSIZE 256
#define TEST_CURVE25519_FIELD_OPS // Exposes Curve25519::isWeakPoint() for testing keys
class CryptoEngine class CryptoEngine
{ {
@ -33,6 +34,8 @@ class CryptoEngine
#if !(MESHTASTIC_EXCLUDE_PKI) #if !(MESHTASTIC_EXCLUDE_PKI)
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey); virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey);
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
#endif #endif
void clearKeys(); void clearKeys();
void setDHPrivateKey(uint8_t *_private_key); void setDHPrivateKey(uint8_t *_private_key);

View File

@ -139,14 +139,24 @@ NodeDB::NodeDB()
crypto->setDHPrivateKey(config.security.private_key.bytes); crypto->setDHPrivateKey(config.security.private_key.bytes);
} else { } else {
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
LOG_INFO("Calculating PKI Public Key\n");
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
LOG_INFO("Generating new PKI keys\n"); LOG_INFO("Generating new PKI keys\n");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
keygenSuccess = true;
}
if (keygenSuccess) {
config.security.public_key.size = 32; config.security.public_key.size = 32;
config.security.private_key.size = 32; config.security.private_key.size = 32;
printBytes("New Pubkey", config.security.public_key.bytes, 32); printBytes("New Pubkey", config.security.public_key.bytes, 32);
owner.public_key.size = 32; owner.public_key.size = 32;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
#else #else
LOG_INFO("No PKI keys set, and generation disabled!\n"); LOG_INFO("No PKI keys set, and generation disabled!\n");
#endif #endif

View File

@ -112,7 +112,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit); sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit);
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 && } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) { (nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
// This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY LOG_INFO("This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY\n");
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(), sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
p->hop_start, p->hop_limit); p->hop_start, p->hop_limit);
} else { } else {

View File

@ -537,6 +537,15 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
case meshtastic_Config_security_tag: case meshtastic_Config_security_tag:
LOG_INFO("Setting config: Security\n"); LOG_INFO("Setting config: Security\n");
config.security = c.payload_variant.security; config.security = c.payload_variant.security;
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN) && !(MESHTASTIC_EXCLUDE_PKI)
// We check for a potentially valid private key, and a blank public key, and regen the public key if needed.
if (config.security.private_key.size == 32 && !memfll(config.security.private_key.bytes, 0, 32) &&
(config.security.public_key.size == 0 || memfll(config.security.public_key.bytes, 0, 32))) {
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; owner.public_key.size = config.security.public_key.size;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size); memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size);
#if !MESHTASTIC_EXCLUDE_PKI #if !MESHTASTIC_EXCLUDE_PKI

View File

@ -167,9 +167,9 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
strcmp(e.channel_id, "PKI") == 0) { strcmp(e.channel_id, "PKI") == 0) {
meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p)); meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p));
meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to); meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to);
// Only accept PKI messages if we have both the sender and receiver in our nodeDB, as then it's likely // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's
// they discovered each other via a channel we have downlink enabled for // likely they discovered each other via a channel we have downlink enabled for
if (tx && tx->has_user && rx && rx->has_user) if (p->to == nodeDB->getNodeNum() || (tx && tx->has_user && rx && rx->has_user))
router->enqueueReceivedMessage(p); router->enqueueReceivedMessage(p);
} else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key
router->enqueueReceivedMessage(p); router->enqueueReceivedMessage(p);
@ -527,7 +527,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
} }
if (ch.settings.uplink_enabled || mp.pki_encrypted) { if (ch.settings.uplink_enabled || mp.pki_encrypted) {
const char *channelId = channels.getGlobalId(chIndex); // FIXME, for now we just use the human name for the channel const char *channelId = mp.pki_encrypted ? "PKI" : channels.getGlobalId(chIndex);
meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed();
env->channel_id = (char *)channelId; env->channel_id = (char *)channelId;
@ -546,12 +546,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
static uint8_t bytes[meshtastic_MeshPacket_size + 64]; static uint8_t bytes[meshtastic_MeshPacket_size + 64];
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env);
std::string topic; std::string topic = cryptTopic + channelId + "/" + owner.id;
if (mp.pki_encrypted) {
topic = cryptTopic + "PKI/" + owner.id;
} else {
topic = cryptTopic + channelId + "/" + owner.id;
}
LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes); LOG_DEBUG("MQTT Publish %s, %u bytes\n", topic.c_str(), numBytes);
publish(topic.c_str(), bytes, numBytes, false); publish(topic.c_str(), bytes, numBytes, false);
@ -561,12 +556,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
// handle json topic // handle json topic
auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded);
if (jsonString.length() != 0) { if (jsonString.length() != 0) {
std::string topicJson; std::string topicJson = jsonTopic + channelId + "/" + owner.id;
if (mp.pki_encrypted) {
topicJson = jsonTopic + "PKI/" + owner.id;
} else {
topicJson = jsonTopic + channelId + "/" + owner.id;
}
LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(),
jsonString.c_str()); jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false); publish(topicJson.c_str(), jsonString.c_str(), false);