Skip to content

Commit e224e5c

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 26bdb41 commit e224e5c

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
@@ -154,15 +154,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
154154
uint8_t data[MAX_PACKET_PAYLOAD];
155155
int macAndDataLen = pkt->payload_len - i;
156156

157-
// Try ECB first (Phase 1: all senders use ECB), then AEAD-4 fallback.
158-
// IMPORTANT: Phase 2 MUST swap to AEAD-first. ECB-first has a 1/65536
159-
// false-positive rate on AEAD packets (nonce bytes matching truncated HMAC),
160-
// producing garbage plaintext. AEAD-first has only 1/2^32 false-positive on
161-
// ECB packets, which is negligible.
162-
int len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen);
163-
if (len <= 0) {
164-
uint8_t assoc[3] = { pkt->header, dest_hash, src_hash };
157+
// Try-both decode: AEAD-first for peers known to support it (avoids 1/65536
158+
// ECB false-positive on AEAD packets), ECB-first for unknown/legacy peers.
159+
uint8_t assoc[3] = { pkt->header, dest_hash, src_hash };
160+
int len;
161+
if (getPeerFlags(j) & CONTACT_FLAG_AEAD) {
165162
len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 3, dest_hash, src_hash);
163+
if (len <= 0) len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen);
164+
} else {
165+
len = Utils::MACThenDecrypt(secret, data, macAndData, macAndDataLen);
166+
if (len <= 0) len = Utils::aeadDecrypt(secret, data, macAndData, macAndDataLen, assoc, 3, dest_hash, src_hash);
166167
}
167168
if (len > 0) { // success!
168169
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -175,7 +176,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
175176
if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) {
176177
if (pkt->isRouteFlood()) {
177178
// send a reciprocal return path to sender, but send DIRECTLY!
178-
mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0);
179+
mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0, getPeerNextAeadNonce(j));
179180
if (rpath) sendDirect(rpath, path, path_len, 500);
180181
}
181182
}
@@ -459,13 +460,13 @@ Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, siz
459460

460461
#define MAX_COMBINED_PATH (MAX_PACKET_PAYLOAD - 2 - CIPHER_BLOCK_SIZE)
461462

462-
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) {
463+
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) {
463464
uint8_t dest_hash[PATH_HASH_SIZE];
464465
dest.copyHashTo(dest_hash);
465-
return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len);
466+
return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len, aead_nonce);
466467
}
467468

468-
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) {
469+
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) {
469470
if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!!
470471

471472
Packet* packet = obtainNewPacket();
@@ -494,17 +495,25 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret,
494495
getRNG()->random(&data[data_len], 4); data_len += 4;
495496
}
496497

497-
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
498+
if (aead_nonce) {
499+
uint8_t dh = packet->payload[0];
500+
uint8_t sh = packet->payload[1];
501+
uint8_t assoc[3] = { packet->header, dh, sh };
502+
len += Utils::aeadEncrypt(secret, &packet->payload[len], data, data_len, assoc, 3, aead_nonce, dh, sh);
503+
} else {
504+
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
505+
}
498506
}
499507

500508
packet->payload_len = len;
501509

502510
return packet;
503511
}
504512

505-
Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) {
513+
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) {
506514
if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE) {
507-
if (data_len + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL;
515+
size_t max_overhead = aead_nonce ? (AEAD_NONCE_SIZE + AEAD_TAG_SIZE) : (CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1);
516+
if (data_len + max_overhead > MAX_PACKET_PAYLOAD) return NULL;
508517
} else {
509518
return NULL; // invalid type
510519
}
@@ -519,7 +528,15 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t*
519528
int len = 0;
520529
len += dest.copyHashTo(&packet->payload[len]); // dest hash
521530
len += self_id.copyHashTo(&packet->payload[len]); // src hash
522-
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
531+
532+
if (aead_nonce) {
533+
uint8_t dest_hash = packet->payload[0];
534+
uint8_t src_hash = packet->payload[1];
535+
uint8_t assoc[3] = { packet->header, dest_hash, src_hash };
536+
len += Utils::aeadEncrypt(secret, &packet->payload[len], data, data_len, assoc, 3, aead_nonce, dest_hash, src_hash);
537+
} else {
538+
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
539+
}
523540

524541
packet->payload_len = len;
525542

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,13 +184,13 @@ 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(uint32_t ack_crc);
189191
Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining);
190-
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);
191-
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);
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, uint16_t aead_nonce=0);
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, uint16_t aead_nonce=0);
192194
Packet* createRawData(const uint8_t* data, size_t len);
193195
Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
194196
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
@@ -199,6 +199,22 @@ void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
199199
}
200200
}
201201

202+
uint8_t BaseChatMesh::getPeerFlags(int peer_idx) {
203+
int i = matching_peer_indexes[peer_idx];
204+
if (i >= 0 && i < num_contacts) {
205+
return contacts[i].flags;
206+
}
207+
return 0;
208+
}
209+
210+
uint16_t BaseChatMesh::getPeerNextAeadNonce(int peer_idx) {
211+
int i = matching_peer_indexes[peer_idx];
212+
if (i >= 0 && i < num_contacts) {
213+
return contacts[i].nextAeadNonce();
214+
}
215+
return 0;
216+
}
217+
202218
void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
203219
int i = matching_peer_indexes[sender_idx];
204220
if (i < 0 || i >= num_contacts) {
@@ -226,7 +242,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
226242
if (packet->isRouteFlood()) {
227243
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
228244
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
229-
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
245+
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4, from.nextAeadNonce());
230246
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
231247
} else {
232248
sendAckTo(from, ack_hash);
@@ -237,7 +253,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
237253

238254
if (packet->isRouteFlood()) {
239255
// let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra)
240-
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0);
256+
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0, from.nextAeadNonce());
241257
if (path) sendFloodScoped(from, path);
242258
}
243259
} else if (flags == TXT_TYPE_SIGNED_PLAIN) {
@@ -253,7 +269,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
253269
if (packet->isRouteFlood()) {
254270
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
255271
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
256-
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
272+
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4, from.nextAeadNonce());
257273
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
258274
} else {
259275
sendAckTo(from, ack_hash);
@@ -269,10 +285,10 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
269285
if (packet->isRouteFlood()) {
270286
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
271287
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
272-
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
288+
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len, from.nextAeadNonce());
273289
if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY);
274290
} else {
275-
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
291+
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len, from.nextAeadNonce());
276292
if (reply) {
277293
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
278294
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
@@ -338,7 +354,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
338354
void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) {
339355
// NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY)
340356
// override this method in various firmwares, if there's a better strategy
341-
mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0);
357+
mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0, contact.nextAeadNonce());
342358
if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay
343359
}
344360

@@ -387,7 +403,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3
387403
temp[len++] = attempt; // hide attempt number at tail end of payload
388404
}
389405

390-
return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len);
406+
return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len, recipient.nextAeadNonce());
391407
}
392408

393409
int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) {
@@ -418,7 +434,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
418434
temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2);
419435
memcpy(&temp[5], text, text_len + 1);
420436

421-
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len);
437+
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len, recipient.nextAeadNonce());
422438
if (pkt == NULL) return MSG_SEND_FAILED;
423439

424440
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
@@ -559,7 +575,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_
559575
memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique
560576
memcpy(&temp[4], req_data, data_len);
561577

562-
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len);
578+
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len, recipient.nextAeadNonce());
563579
}
564580
if (pkt) {
565581
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
@@ -586,7 +602,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u
586602
memset(&temp[5], 0, 4); // reserved (possibly for 'since' param)
587603
getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique
588604

589-
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp));
605+
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp), recipient.nextAeadNonce());
590606
}
591607
if (pkt) {
592608
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
@@ -709,7 +725,7 @@ void BaseChatMesh::checkConnections() {
709725
// calc expected ACK reply
710726
mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE);
711727

712-
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9);
728+
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9, contact->nextAeadNonce());
713729
if (pkt) {
714730
sendDirect(pkt, contact->out_path, contact->out_path_len);
715731
}

src/helpers/BaseChatMesh.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ class BaseChatMesh : public mesh::Mesh {
125125
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override;
126126
int searchPeersByHash(const uint8_t* hash) override;
127127
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
128+
uint8_t getPeerFlags(int peer_idx) override;
129+
uint16_t getPeerNextAeadNonce(int peer_idx) override;
128130
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
129131
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;
130132
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
@@ -15,7 +15,14 @@ struct ContactInfo {
1515
uint32_t lastmod; // by OUR clock
1616
int32_t gps_lat, gps_lon; // 6 dec places
1717
uint32_t sync_since;
18-
uint16_t aead_nonce; // per-peer AEAD nonce counter for DMs (not used for group messages), seeded from HW RNG
18+
mutable uint16_t aead_nonce; // per-peer AEAD nonce counter for DMs (not used for group messages), seeded from HW RNG
19+
20+
// Returns next AEAD nonce (post-increment) if peer supports AEAD, 0 otherwise.
21+
// When 0, callers use ECB encryption.
22+
uint16_t nextAeadNonce() const {
23+
if (flags & CONTACT_FLAG_AEAD) return ++aead_nonce;
24+
return 0;
25+
}
1926

2027
const uint8_t* getSharedSecret(const mesh::LocalIdentity& self_id) const {
2128
if (!shared_secret_valid) {

0 commit comments

Comments
 (0)