From c8d4b250f431a2dba5da95faa58cd974b6f27dfd Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 12 Feb 2026 02:16:56 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20AEAD-4=20assoc=20data=20mismatch=20?= =?UTF-8?q?=E2=80=94=20route=20type=20bits=20set=20after=20encryption?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header's route type bits (PH_ROUTE_MASK) are zero when createDatagram/createPathReturn encrypt with AEAD, but get changed to ROUTE_TYPE_FLOOD (1) or ROUTE_TYPE_DIRECT (2) by sendFlood/sendDirect afterwards. The receiver builds assoc from the received header (with route bits set), so the tag check always fails and every AEAD packet is silently dropped. Mask out route type bits in assoc data on all 5 encrypt/decrypt sites. Also track AEAD decode success to enable peer capability auto-detection. --- src/Mesh.cpp | 21 ++++++++++++++------- src/Mesh.h | 1 + 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 7fc21fde5..e8b5f3b87 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -158,16 +158,23 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // Try-both decode: AEAD-first for peers known to support it (avoids 1/65536 // ECB false-positive on AEAD packets), ECB-first for unknown/legacy peers. - uint8_t assoc[3] = { pkt->header, dest_hash, src_hash }; + // Mask out route type bits — they are set after encryption and vary per hop. + uint8_t assoc[3] = { (uint8_t)(pkt->header & ~PH_ROUTE_MASK), dest_hash, src_hash }; int len; + bool decoded_aead = false; if (getPeerFlags(j) & CONTACT_FLAG_AEAD) { len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 3, dest_hash, src_hash); - if (len <= 0) len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen); + if (len > 0) decoded_aead = true; + else len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen); } else { len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen); - if (len <= 0) len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 3, dest_hash, src_hash); + if (len <= 0) { + len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 3, dest_hash, src_hash); + if (len > 0) decoded_aead = true; + } } if (len > 0) { // success! + if (decoded_aead) onPeerAeadDetected(j); if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { int k = 0; uint8_t path_len = data[k++]; @@ -228,7 +235,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // Phase 2 MUST swap to AEAD-first (see peer message comment above). int len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen); if (len <= 0) { - uint8_t assoc[2] = { pkt->header, dest_hash }; + uint8_t assoc[2] = { (uint8_t)(pkt->header & ~PH_ROUTE_MASK), dest_hash }; len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 2, dest_hash, 0); } if (len > 0) { // success! @@ -265,7 +272,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // worthwhile for public/hashtag channels where the PSK is already widely known. int len = Utils::MACThenDecrypt(channels[j].secret, data, macAndData, macAndDataLen); if (len <= 0) { - uint8_t assoc[2] = { pkt->header, channel_hash }; + uint8_t assoc[2] = { (uint8_t)(pkt->header & ~PH_ROUTE_MASK), channel_hash }; len = Utils::aeadDecrypt(channels[j].secret, data, macAndData, macAndDataLen, assoc, 2, channel_hash, 0); } if (len > 0) { // success! @@ -508,7 +515,7 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, if (aead_nonce) { uint8_t dh = packet->payload[0]; uint8_t sh = packet->payload[1]; - uint8_t assoc[3] = { packet->header, dh, sh }; + uint8_t assoc[3] = { (uint8_t)(packet->header & ~PH_ROUTE_MASK), dh, sh }; len += Utils::aeadEncrypt(secret, &packet->payload[len], data, data_len, assoc, 3, aead_nonce, dh, sh); } else { len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); @@ -543,7 +550,7 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* if (aead_nonce) { uint8_t dest_hash = packet->payload[0]; uint8_t src_hash = packet->payload[1]; - uint8_t assoc[3] = { packet->header, dest_hash, src_hash }; + uint8_t assoc[3] = { (uint8_t)(packet->header & ~PH_ROUTE_MASK), dest_hash, src_hash }; len += Utils::aeadEncrypt(secret, &packet->payload[len], data, data_len, assoc, 3, aead_nonce, dest_hash, src_hash); } else { len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); diff --git a/src/Mesh.h b/src/Mesh.h index 3e8245f94..7d0cef28e 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -85,6 +85,7 @@ protected: virtual void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } virtual uint8_t getPeerFlags(int peer_idx) { return 0; } virtual uint16_t getPeerNextAeadNonce(int peer_idx) { return 0; } + virtual void onPeerAeadDetected(int peer_idx) { } /** * \brief A (now decrypted) data packet has been received (by a known peer).