Skip to content

Commit 376e0bd

Browse files
committed
Enable AEAD-4 sending to peers that advertise support
Send ChaChaPoly-encrypted messages to peers with CONTACT_FLAG_AEAD set, and try AEAD decode first for those peers (avoiding 1/65536 ECB false-positive). Legacy peers continue to use ECB in both directions. - Add aead_nonce parameter to createDatagram/createPathReturn (default 0 = ECB) - Add getPeerFlags/getPeerNextAeadNonce virtual methods for decode-order selection - Add ContactInfo::nextAeadNonce() helper (returns nonce++ if AEAD, 0 otherwise) - Update all BaseChatMesh send paths to pass nonce for AEAD-capable peers - Adaptive decode order: AEAD-first for known AEAD peers, ECB-first for others
1 parent bc82d30 commit 376e0bd

5 files changed

Lines changed: 75 additions & 31 deletions

File tree

src/Mesh.cpp

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
149149
uint8_t data[MAX_PACKET_PAYLOAD];
150150
int macAndDataLen = pkt->payload_len - i;
151151

152-
// Try ECB first (Phase 1: all senders use ECB), then AEAD-4 fallback.
153-
// IMPORTANT: Phase 2 MUST swap to AEAD-first. ECB-first has a 1/65536
154-
// false-positive rate on AEAD packets (nonce bytes matching truncated HMAC),
155-
// producing garbage plaintext. AEAD-first has only 1/2^32 false-positive on
156-
// ECB packets, which is negligible.
157-
int len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen);
158-
if (len <= 0) {
159-
uint8_t assoc[3] = { pkt->header, dest_hash, src_hash };
152+
// Try-both decode: AEAD-first for peers known to support it (avoids 1/65536
153+
// ECB false-positive on AEAD packets), ECB-first for unknown/legacy peers.
154+
uint8_t assoc[3] = { pkt->header, dest_hash, src_hash };
155+
int len;
156+
if (getPeerFlags(j) & CONTACT_FLAG_AEAD) {
160157
len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 3, dest_hash, src_hash);
158+
if (len <= 0) len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen);
159+
} else {
160+
len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen);
161+
if (len <= 0) len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 3, dest_hash, src_hash);
161162
}
162163
if (len > 0) { // success!
163164
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -172,7 +173,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
172173
if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) {
173174
if (pkt->isRouteFlood()) {
174175
// send a reciprocal return path to sender, but send DIRECTLY!
175-
mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0);
176+
mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0, getPeerNextAeadNonce(j));
176177
if (rpath) sendDirect(rpath, path, path_len, 500);
177178
}
178179
}
@@ -449,13 +450,13 @@ Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, siz
449450

450451
#define MAX_COMBINED_PATH (MAX_PACKET_PAYLOAD - 2 - CIPHER_BLOCK_SIZE)
451452

452-
Packet* Mesh::createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len) {
453+
Packet* Mesh::createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len, uint16_t aead_nonce) {
453454
uint8_t dest_hash[PATH_HASH_SIZE];
454455
dest.copyHashTo(dest_hash);
455-
return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len);
456+
return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len, aead_nonce);
456457
}
457458

458-
Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len) {
459+
Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len, uint16_t aead_nonce) {
459460
uint8_t path_hash_size = (path_len >> 6) + 1;
460461
uint8_t path_hash_count = path_len & 63;
461462

@@ -487,17 +488,25 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret,
487488
getRNG()->random(&data[data_len], 4); data_len += 4;
488489
}
489490

490-
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
491+
if (aead_nonce) {
492+
uint8_t dh = packet->payload[0];
493+
uint8_t sh = packet->payload[1];
494+
uint8_t assoc[3] = { packet->header, dh, sh };
495+
len += Utils::aeadEncrypt(secret, &packet->payload[len], data, data_len, assoc, 3, aead_nonce, dh, sh);
496+
} else {
497+
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
498+
}
491499
}
492500

493501
packet->payload_len = len;
494502

495503
return packet;
496504
}
497505

498-
Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) {
506+
Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len, uint16_t aead_nonce) {
499507
if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE) {
500-
if (data_len + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL;
508+
size_t max_overhead = aead_nonce ? (AEAD_NONCE_SIZE + AEAD_TAG_SIZE) : (CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1);
509+
if (data_len + max_overhead > MAX_PACKET_PAYLOAD) return NULL;
501510
} else {
502511
return NULL; // invalid type
503512
}
@@ -512,7 +521,15 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t*
512521
int len = 0;
513522
len += dest.copyHashTo(&packet->payload[len]); // dest hash
514523
len += self_id.copyHashTo(&packet->payload[len]); // src hash
515-
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
524+
525+
if (aead_nonce) {
526+
uint8_t dest_hash = packet->payload[0];
527+
uint8_t src_hash = packet->payload[1];
528+
uint8_t assoc[3] = { packet->header, dest_hash, src_hash };
529+
len += Utils::aeadEncrypt(secret, &packet->payload[len], data, data_len, assoc, 3, aead_nonce, dest_hash, src_hash);
530+
} else {
531+
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
532+
}
516533

517534
packet->payload_len = len;
518535

src/Mesh.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ class Mesh : public Dispatcher {
8282
* \param peer_idx index of peer, [0..n) where n is what searchPeersByHash() returned
8383
*/
8484
virtual void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { }
85+
virtual uint8_t getPeerFlags(int peer_idx) { return 0; }
86+
virtual uint16_t getPeerNextAeadNonce(int peer_idx) { return 0; }
8587

8688
/**
8789
* \brief A (now decrypted) data packet has been received (by a known peer).
@@ -182,15 +184,15 @@ class Mesh : public Dispatcher {
182184
RTCClock* getRTCClock() const { return _rtc; }
183185

184186
Packet* createAdvert(const LocalIdentity& id, const uint8_t* app_data=NULL, size_t app_data_len=0);
185-
Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len);
187+
Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len, uint16_t aead_nonce=0);
186188
Packet* createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len);
187189
Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len);
188190
Packet* createAck(const uint8_t* ack, uint8_t len);
189191
Packet* createAck(uint32_t ack_crc) { return createAck((uint8_t *) &ack_crc, 4); }
190192
Packet* createMultiAck(const uint8_t* ack, uint8_t len, uint8_t remaining);
191193
Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining) { return createMultiAck((uint8_t *)&ack_crc, 4, remaining); }
192-
Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
193-
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
194+
Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len, uint16_t aead_nonce=0);
195+
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len, uint16_t aead_nonce=0);
194196
Packet* createRawData(const uint8_t* data, size_t len);
195197
Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
196198
Packet* createControlData(const uint8_t* data, size_t len);

src/helpers/BaseChatMesh.cpp

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,22 @@ void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
208208
}
209209
}
210210

211+
uint8_t BaseChatMesh::getPeerFlags(int peer_idx) {
212+
int i = matching_peer_indexes[peer_idx];
213+
if (i >= 0 && i < num_contacts) {
214+
return contacts[i].flags;
215+
}
216+
return 0;
217+
}
218+
219+
uint16_t BaseChatMesh::getPeerNextAeadNonce(int peer_idx) {
220+
int i = matching_peer_indexes[peer_idx];
221+
if (i >= 0 && i < num_contacts) {
222+
return contacts[i].nextAeadNonce();
223+
}
224+
return 0;
225+
}
226+
211227
void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
212228
int i = matching_peer_indexes[sender_idx];
213229
if (i < 0 || i >= num_contacts) {
@@ -239,7 +255,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
239255
if (packet->isRouteFlood()) {
240256
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
241257
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
242-
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 6);
258+
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 6, from.nextAeadNonce());
243259
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
244260
} else {
245261
sendAckTo(from, ack_hash, 6);
@@ -250,7 +266,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
250266

251267
if (packet->isRouteFlood()) {
252268
// let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra)
253-
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0);
269+
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0, from.nextAeadNonce());
254270
if (path) sendFloodScoped(from, path);
255271
}
256272
} else if (flags == TXT_TYPE_SIGNED_PLAIN) {
@@ -266,7 +282,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
266282
if (packet->isRouteFlood()) {
267283
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
268284
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
269-
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
285+
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4, from.nextAeadNonce());
270286
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
271287
} else {
272288
sendAckTo(from, (uint8_t *) &ack_hash);
@@ -282,10 +298,10 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
282298
if (packet->isRouteFlood()) {
283299
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
284300
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
285-
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
301+
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len, from.nextAeadNonce());
286302
if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY);
287303
} else {
288-
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
304+
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len, from.nextAeadNonce());
289305
if (reply) {
290306
if (from.out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
291307
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
@@ -351,7 +367,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
351367
void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) {
352368
// NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY)
353369
// override this method in various firmwares, if there's a better strategy
354-
mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0);
370+
mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0, contact.nextAeadNonce());
355371
if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay
356372
}
357373

@@ -427,7 +443,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3
427443
temp[len++] = attempt; // hide attempt number at tail end of payload
428444
}
429445

430-
return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len);
446+
return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len, recipient.nextAeadNonce());
431447
}
432448

433449
int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) {
@@ -458,7 +474,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
458474
temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2);
459475
memcpy(&temp[5], text, text_len + 1);
460476

461-
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len);
477+
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len, recipient.nextAeadNonce());
462478
if (pkt == NULL) return MSG_SEND_FAILED;
463479

464480
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
@@ -630,7 +646,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_
630646
memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique
631647
memcpy(&temp[4], req_data, data_len);
632648

633-
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len);
649+
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len, recipient.nextAeadNonce());
634650
}
635651
if (pkt) {
636652
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
@@ -657,7 +673,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u
657673
memset(&temp[5], 0, 4); // reserved (possibly for 'since' param)
658674
getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique
659675

660-
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp));
676+
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp), recipient.nextAeadNonce());
661677
}
662678
if (pkt) {
663679
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
@@ -780,7 +796,7 @@ void BaseChatMesh::checkConnections() {
780796
// calc expected ACK reply
781797
mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE);
782798

783-
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9);
799+
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9, contact->nextAeadNonce());
784800
if (pkt) {
785801
sendDirect(pkt, contact->out_path, contact->out_path_len);
786802
}

src/helpers/BaseChatMesh.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ class BaseChatMesh : public mesh::Mesh {
128128
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override;
129129
int searchPeersByHash(const uint8_t* hash) override;
130130
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
131+
uint8_t getPeerFlags(int peer_idx) override;
132+
uint16_t getPeerNextAeadNonce(int peer_idx) override;
131133
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
132134
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
133135
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;

src/helpers/ContactInfo.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ struct ContactInfo {
1717
uint32_t lastmod; // by OUR clock
1818
int32_t gps_lat, gps_lon; // 6 dec places
1919
uint32_t sync_since;
20-
uint16_t aead_nonce; // per-peer AEAD nonce counter for DMs (not used for group messages), seeded from HW RNG
20+
mutable uint16_t aead_nonce; // per-peer AEAD nonce counter for DMs (not used for group messages), seeded from HW RNG
21+
22+
// Returns next AEAD nonce (post-increment) if peer supports AEAD, 0 otherwise.
23+
// When 0, callers use ECB encryption.
24+
uint16_t nextAeadNonce() const {
25+
if (flags & CONTACT_FLAG_AEAD) return ++aead_nonce;
26+
return 0;
27+
}
2128

2229
const uint8_t* getSharedSecret(const mesh::LocalIdentity& self_id) const {
2330
if (!shared_secret_valid) {

0 commit comments

Comments
 (0)