Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/graphics/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
}

// Called to trigger a banner with custom message and duration
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, bool useBase16,
std::function<void(uint32_t)> bannerCallback)
{
#ifdef USE_EINK
Expand All @@ -356,7 +356,10 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
NotificationRenderer::alertBannerCallback = bannerCallback;
NotificationRenderer::pauseBanner = false;
NotificationRenderer::curSelected = 0;
NotificationRenderer::current_notification_type = notificationTypeEnum::number_picker;
if (useBase16)
NotificationRenderer::current_notification_type = notificationTypeEnum::hex_picker;
else
NotificationRenderer::current_notification_type = notificationTypeEnum::number_picker;
NotificationRenderer::numDigits = digits;
NotificationRenderer::currentNumber = 0;

Expand Down
3 changes: 2 additions & 1 deletion src/graphics/Screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ class Screen : public concurrency::OSThread
void showOverlayBanner(BannerOverlayOptions);

void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, bool useBase16,
std::function<void(uint32_t)> bannerCallback);
void showTextInput(const char *header, const char *initialText, uint32_t durationMs,
std::function<void(const std::string &)> textCallback);

Expand Down
9 changes: 5 additions & 4 deletions src/graphics/draw/MenuHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2323,8 +2323,10 @@ void menuHandler::testMenu()

void menuHandler::numberTest()
{
screen->showNumberPicker("Pick a number\n ", 30000, 4,
[](int number_picked) -> void { LOG_WARN("Nodenum: %u", number_picked); });
screen->showNumberPicker("Verify Nodenum:\n ", 30000, 8, true, [](int number_picked) -> void {
LOG_WARN("Nodenum: 0x%08x", number_picked);
keyVerificationModule->sendInitialRequest(number_picked);
});
Comment thread
thebentern marked this conversation as resolved.
Outdated
}

void menuHandler::wifiBaseMenu()
Expand Down Expand Up @@ -2508,8 +2510,7 @@ void menuHandler::keyVerificationFinalPrompt()
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
keyVerificationModule->commitVerifiedRemoteNode();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
};
screen->showOverlayBanner(options);
Expand Down
23 changes: 23 additions & 0 deletions src/mesh/CryptoEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,29 @@ bool CryptoEngine::setDHPublicKey(uint8_t *pubKey)
return true;
}

void CryptoEngine::setPendingPublicKey(uint32_t node, const uint8_t *key)
{
pendingKeyVerificationNode = node;
memcpy(pendingKeyVerificationPublicKey, key, 32);
hasPendingKeyVerificationKey = true;
}

void CryptoEngine::clearPendingPublicKey()
{
pendingKeyVerificationNode = 0;
memset(pendingKeyVerificationPublicKey, 0, 32);
hasPendingKeyVerificationKey = false;
}

bool CryptoEngine::getPendingPublicKey(uint32_t node, meshtastic_NodeInfoLite_public_key_t &out)
{
if (!hasPendingKeyVerificationKey || node == 0 || node != pendingKeyVerificationNode)
return false;
out.size = 32;
memcpy(out.bytes, pendingKeyVerificationPublicKey, 32);
return true;
}
Comment thread
thebentern marked this conversation as resolved.

#endif
concurrency::Lock *cryptLock;

Expand Down
12 changes: 12 additions & 0 deletions src/mesh/CryptoEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ class CryptoEngine
virtual bool setDHPublicKey(uint8_t *publicKey);
virtual void hash(uint8_t *bytes, size_t numBytes);

// Temporary holder for a peer's not-yet-verified public key, learned in-band during an
// in-progress key-verification handshake before it is committed to NodeDB. Lets the Router
// run the DH handshake to encode/decode the follow-on PKI packet. Single slot is enough:
// only one verification runs at a time. Discarded when the handshake ends (resetToIdle).
void setPendingPublicKey(uint32_t node, const uint8_t *key);
void clearPendingPublicKey();
// Fills `out` (size set to 32) and returns true iff a pending key is held for `node`.
bool getPendingPublicKey(uint32_t node, meshtastic_NodeInfoLite_public_key_t &out);

virtual void aesSetKey(const uint8_t *key, size_t key_len);

virtual void aesEncrypt(uint8_t *in, uint8_t *out);
Expand Down Expand Up @@ -94,6 +103,9 @@ class CryptoEngine
#if !(MESHTASTIC_EXCLUDE_PKI)
uint8_t shared_key[32] = {0};
uint8_t private_key[32] = {0};
uint32_t pendingKeyVerificationNode = 0;
uint8_t pendingKeyVerificationPublicKey[32] = {0};
bool hasPendingKeyVerificationKey = false;
#if !(MESHTASTIC_EXCLUDE_XEDDSA)
uint8_t xeddsa_public_key[32] = {0};
uint8_t xeddsa_private_key[32] = {0};
Expand Down
80 changes: 60 additions & 20 deletions src/mesh/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,36 +521,63 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
bool decrypted = false;
ChannelIndex chIndex = 0;
#if !(MESHTASTIC_EXCLUDE_PKI)
// Attempt PKI decryption first. The sender's key may come from the hot
// store or the warm tier (nodes evicted from the hot store keep their key
// there), so DMs from long-tail nodes still decrypt.
meshtastic_NodeInfoLite_public_key_t fromKey = {0, {0}};
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->copyPublicKey(p->from, fromKey) &&
nodeDB->getMeshNode(p->to) != nullptr && nodeDB->getMeshNode(p->to)->public_key.size > 0 &&
rawSize > MESHTASTIC_PKC_OVERHEAD) {
LOG_DEBUG("Attempt PKI decryption");

if (crypto->decryptCurve25519(p->from, fromKey, p->id, rawSize, p->encrypted.bytes, bytes)) {
LOG_INFO("PKI Decryption worked!");
// Resolve the sender's public key: prefer the one stored in NodeDB (hot store or warm tier), else
// fall back to a not-yet-committed key held during an in-progress key-verification handshake.
meshtastic_NodeInfoLite_public_key_t remotePublic = {0, {0}};
bool haveRemoteKey = nodeDB->copyPublicKey(p->from, remotePublic) || crypto->getPendingPublicKey(p->from, remotePublic);

meshtastic_NodeInfoLite *ourNode = nullptr;
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && rawSize > MESHTASTIC_PKC_OVERHEAD &&
(ourNode = nodeDB->getMeshNode(p->to)) != nullptr && ourNode->public_key.size > 0) {
// If we know the sender's key, make a single attempt with it. Otherwise fall back to trying each
// configured admin key, so an authorized admin can reach a node that has not yet learned their
// key. decryptCurve25519 is AES-CCM AEAD, so a wrong key fails authentication and we try the next.
bool viaAdminKey = false;
if (crypto->decryptCurve25519(p->from, remotePublic, p->id, rawSize, p->encrypted.bytes, bytes)) {
decrypted = true;
}
for (int i = 0; i < 3 && !decrypted; i++) {
if (config.security.admin_key[i].size != 32)
continue;
Comment thread
jp-bennett marked this conversation as resolved.
remotePublic.size = 32;
memcpy(remotePublic.bytes, config.security.admin_key[i].bytes, 32);

if (crypto->decryptCurve25519(p->from, remotePublic, p->id, rawSize, p->encrypted.bytes, bytes)) {
decrypted = true;
viaAdminKey = true;
break; // stop after first successful decryption
}
}
if (decrypted) {
LOG_INFO("PKI Decryption worked!");
meshtastic_Data decodedtmp;
memset(&decodedtmp, 0, sizeof(decodedtmp));
rawSize -= MESHTASTIC_PKC_OVERHEAD;
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
size_t payloadSize = rawSize - MESHTASTIC_PKC_OVERHEAD;
if (pb_decode_from_bytes(bytes, payloadSize, &meshtastic_Data_msg, &decodedtmp) &&
decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
decrypted = true;
rawSize = payloadSize; // commit the overhead subtraction only on full success
LOG_INFO("Packet decrypted using PKI!");
p->pki_encrypted = true;
memcpy(p->public_key.bytes, fromKey.bytes, 32);
memcpy(p->public_key.bytes, remotePublic.bytes, 32);
p->public_key.size = 32;
p->decoded = decodedtmp;
Comment thread
jp-bennett marked this conversation as resolved.
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
if (viaAdminKey) {
// We decrypted this using a configured admin key, so we had no pubkey from the sender.
// Persist the key so future packets take the fast path and we can PKI-reply. The from
// nodenum is bound into the AEAD nonce, so this NodeNum->key binding is authenticated
// by the (trusted) admin that produced the packet.
meshtastic_NodeInfoLite *fromNode = nodeDB->getOrCreateMeshNode(p->from);
if (fromNode != nullptr)
fromNode->public_key = remotePublic;
}
} else {
// AEAD already authenticated this ciphertext, so no other candidate could decode it -
// the payload is simply malformed.
LOG_ERROR("PKC Decrypted, but pb_decode failed!");
return DecodeState::DECODE_FAILURE;
}
} else {
LOG_WARN("PKC decrypt attempted but failed!");
}
}
#endif
Expand Down Expand Up @@ -755,10 +782,17 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it

#if !(MESHTASTIC_EXCLUDE_PKI)
// Destination key from the hot store or the warm tier (evicted
// long-tail nodes keep their key there)
// Resolve the destination's public key: prefer NodeDB (hot store or warm tier - evicted
// long-tail nodes keep their key there), otherwise (for a key-verification follow-on packet
// that explicitly requested PKI) fall back to the not-yet-verified key held during an
// in-progress handshake. This lets us DH-encode the follow-on packet before the peer's key
// has been committed to NodeDB.
meshtastic_NodeInfoLite_public_key_t destKey = {0, {0}};
bool haveDestKey = nodeDB->copyPublicKey(p->to, destKey);
if (!haveDestKey && p->pki_encrypted && p->decoded.portnum == meshtastic_PortNum_KEY_VERIFICATION_APP &&
crypto->getPendingPublicKey(p->to, destKey)) {
haveDestKey = true;
}
// We may want to retool things so we can send a PKC packet when the client specifies a key and nodenum, even if the node
// is not in the local nodedb
// First, only PKC encrypt packets we are originating
Expand All @@ -776,11 +810,17 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
config.security.private_key.size == 32 && !isBroadcast(p->to) &&
// Some portnums either make no sense to send with PKC
p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP &&
// We allow Key Verification messages to be sent without a known destination key, since the point of those messages is
// to exchange keys. The first exchange (no usable key yet) falls through to channel encryption; the follow-on packet
// uses the pending key resolved into haveDestKey/destKey above.
// Though possible the first packet each direction should go non-pkc
// to handle the case where the remote node has our key, but we don't have theirs.
!(p->decoded.portnum == meshtastic_PortNum_KEY_VERIFICATION_APP && !haveDestKey)) {
LOG_DEBUG("Use PKI!");
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
return meshtastic_Routing_Error_TOO_LARGE;
// Check for a known public key for the destination
// Check for a usable public key for the destination (NodeDB or a pending key-verification key)
if (!haveDestKey) {
LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to,
p->decoded.portnum);
Expand Down
Loading
Loading