Skip to content

Commit 9401a67

Browse files
committed
Integrate Ascon encryption into mesh packet layer
1 parent c643ce0 commit 9401a67

4 files changed

Lines changed: 178 additions & 58 deletions

File tree

src/Mesh.cpp

Lines changed: 151 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace mesh {
55

66
void Mesh::begin() {
77
Dispatcher::begin();
8+
// Initialize Ascon counter with random starting value for replay protection
9+
Utils::initAsconCounter(*_rng);
810
}
911

1012
void Mesh::loop() {
@@ -46,6 +48,11 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
4648

4749
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) {
4850
if (pkt->path_len < MAX_PATH_SIZE) {
51+
// TRACE packet minimum: trace_tag(4) + auth_code(4) + flags(1) = 9 bytes
52+
if (pkt->payload_len < 9) {
53+
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete TRACE packet", getLogDateTime());
54+
return ACTION_RELEASE;
55+
}
4956
uint8_t i = 0;
5057
uint32_t trace_tag;
5158
memcpy(&trace_tag, &pkt->payload[i], 4); i += 4;
@@ -56,9 +63,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
5663

5764
uint8_t len = pkt->payload_len - i;
5865
uint8_t offset = pkt->path_len << path_sz;
66+
uint8_t hash_size = 1 << path_sz;
5967
if (offset >= len) { // TRACE has reached end of given path
6068
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
61-
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
69+
} else if (i + offset + hash_size <= pkt->payload_len && self_id.isHashMatch(&pkt->payload[i + offset], hash_size) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
6270
// append SNR (Not hash!)
6371
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
6472

@@ -80,10 +88,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
8088
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
8189
// check for 'early received' ACK
8290
if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
83-
int i = 0;
84-
uint32_t ack_crc;
85-
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
86-
if (i <= pkt->payload_len) {
91+
if (pkt->payload_len >= 4) {
92+
uint32_t ack_crc;
93+
memcpy(&ack_crc, &pkt->payload[0], 4);
8794
onAckRecv(pkt, ack_crc);
8895
}
8996
}
@@ -115,12 +122,11 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
115122

116123
switch (pkt->getPayloadType()) {
117124
case PAYLOAD_TYPE_ACK: {
118-
int i = 0;
119-
uint32_t ack_crc;
120-
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
121-
if (i > pkt->payload_len) {
125+
if (pkt->payload_len < 4) {
122126
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete ACK packet", getLogDateTime());
123127
} else if (!_tables->hasSeen(pkt)) {
128+
uint32_t ack_crc;
129+
memcpy(&ack_crc, &pkt->payload[0], 4);
124130
onAckRecv(pkt, ack_crc);
125131
action = routeRecvPacket(pkt);
126132
}
@@ -134,8 +140,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
134140
uint8_t dest_hash = pkt->payload[i++];
135141
uint8_t src_hash = pkt->payload[i++];
136142

137-
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
138-
if (i + CIPHER_MAC_SIZE >= pkt->payload_len) {
143+
uint8_t* encryptedData = &pkt->payload[i]; // encrypted data (AES: MAC + ciphertext, Ascon: counter + ciphertext + tag)
144+
int encrypted_len = pkt->payload_len - i;
145+
146+
if (encrypted_len < CIPHER_MAC_SIZE) {
139147
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
140148
} else if (!_tables->hasSeen(pkt)) {
141149
// NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins.
@@ -151,10 +159,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
151159
uint8_t secret[PUB_KEY_SIZE];
152160
getPeerSharedSecret(secret, j);
153161

154-
// decrypt, checking MAC is valid
162+
// Unified decryption: tries Ascon first, falls back to legacy AES-ECB+HMAC
155163
uint8_t data[MAX_PACKET_PAYLOAD];
156-
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
164+
bool was_ascon = false;
165+
int len = Utils::decryptAuto(secret, data, encryptedData, encrypted_len, &was_ascon);
166+
157167
if (len > 0) { // success!
168+
// Notify subclass of detected crypto capability (for auto-upgrade to Ascon)
169+
onPeerAsconCapabilityDetected(j, was_ascon);
170+
158171
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
159172
int k = 0;
160173
uint8_t path_len = data[k++];
@@ -164,17 +177,23 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
164177
uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!)
165178
if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) {
166179
if (pkt->isRouteFlood()) {
167-
// send a reciprocal return path to sender, but send DIRECTLY!
168-
mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0);
180+
// send a reciprocal return path to sender using same crypto they used, but send DIRECTLY!
181+
mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0, was_ascon);
169182
if (rpath) sendDirect(rpath, path, path_len, 500);
170183
}
171184
}
172185
} else {
173186
onPeerDataRecv(pkt, pkt->getPayloadType(), j, secret, data, len);
174187
}
188+
// SECURITY: Clear sensitive data from stack before breaking
189+
memset(data, 0, sizeof(data));
190+
memset(secret, 0, sizeof(secret));
175191
found = true;
176192
break;
177193
}
194+
// SECURITY: Clear buffers even if decryption failed
195+
memset(data, 0, sizeof(data));
196+
memset(secret, 0, sizeof(secret));
178197
}
179198
if (found) {
180199
pkt->markDoNotRetransmit(); // packet was for this node, so don't retransmit
@@ -191,8 +210,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
191210
uint8_t dest_hash = pkt->payload[i++];
192211
uint8_t* sender_pub_key = &pkt->payload[i]; i += PUB_KEY_SIZE;
193212

194-
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
195-
if (i + 2 >= pkt->payload_len) {
213+
uint8_t* encryptedData = &pkt->payload[i]; // encrypted data (AES: MAC + ciphertext, Ascon: counter + ciphertext + tag)
214+
int encrypted_len = pkt->payload_len - i;
215+
216+
if (encrypted_len < CIPHER_MAC_SIZE + 1) {
196217
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
197218
} else if (!_tables->hasSeen(pkt)) {
198219
if (self_id.isHashMatch(&dest_hash)) {
@@ -201,39 +222,51 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
201222
uint8_t secret[PUB_KEY_SIZE];
202223
self_id.calcSharedSecret(secret, sender);
203224

204-
// decrypt, checking MAC is valid
225+
// Unified decryption: tries Ascon first, falls back to legacy AES-ECB+HMAC
205226
uint8_t data[MAX_PACKET_PAYLOAD];
206-
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
227+
bool was_ascon = false;
228+
int len = Utils::decryptAuto(secret, data, encryptedData, encrypted_len, &was_ascon);
229+
207230
if (len > 0) { // success!
208-
onAnonDataRecv(pkt, secret, sender, data, len);
231+
onAnonDataRecv(pkt, secret, sender, data, len, was_ascon);
209232
pkt->markDoNotRetransmit();
210233
}
234+
// SECURITY: Clear sensitive data from stack
235+
memset(data, 0, sizeof(data));
236+
memset(secret, 0, sizeof(secret));
211237
}
212238
action = routeRecvPacket(pkt);
213239
}
214240
break;
215241
}
216-
case PAYLOAD_TYPE_GRP_DATA:
242+
case PAYLOAD_TYPE_GRP_DATA:
217243
case PAYLOAD_TYPE_GRP_TXT: {
218244
int i = 0;
219245
uint8_t channel_hash = pkt->payload[i++];
220246

221-
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
222-
if (i + 2 >= pkt->payload_len) {
247+
uint8_t* encryptedData = &pkt->payload[i]; // encrypted data (AES: MAC + ciphertext, Ascon: counter + ciphertext + tag)
248+
int encrypted_len = pkt->payload_len - i;
249+
250+
if (encrypted_len < 2) {
223251
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
224252
} else if (!_tables->hasSeen(pkt)) {
225253
// scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM)
226254
GroupChannel channels[4];
227255
int num = searchChannelsByHash(&channel_hash, channels, 4);
228256
// for each matching channel, try to decrypt data
229257
for (int j = 0; j < num; j++) {
230-
// decrypt, checking MAC is valid
258+
// Unified decryption: tries Ascon first, falls back to legacy AES-ECB+HMAC
231259
uint8_t data[MAX_PACKET_PAYLOAD];
232-
int len = Utils::MACThenDecrypt(channels[j].secret, data, macAndData, pkt->payload_len - i);
260+
int len = Utils::decryptAuto(channels[j].secret, data, encryptedData, encrypted_len);
261+
233262
if (len > 0) { // success!
234263
onGroupDataRecv(pkt, pkt->getPayloadType(), channels[j], data, len);
264+
// SECURITY: Clear sensitive data from stack before breaking
265+
memset(data, 0, sizeof(data));
235266
break;
236267
}
268+
// SECURITY: Clear buffer even if decryption failed
269+
memset(data, 0, sizeof(data));
237270
}
238271
action = routeRecvPacket(pkt);
239272
}
@@ -430,23 +463,38 @@ Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, siz
430463
return packet;
431464
}
432465

433-
#define MAX_COMBINED_PATH (MAX_PACKET_PAYLOAD - 2 - CIPHER_BLOCK_SIZE)
434-
435-
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) {
466+
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, bool use_ascon) {
436467
uint8_t dest_hash[PATH_HASH_SIZE];
437468
dest.copyHashTo(dest_hash);
438-
return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len);
469+
return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len, use_ascon);
439470
}
440471

441-
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) {
442-
if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!!
472+
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, bool use_ascon) {
473+
// Plaintext layout: [path_len (1)] [path (path_len)] [extra_type (1)] [extra (extra_len)]
474+
// Or if no extra: [dummy_type (1)] [rand (4)]
475+
const int plain_len = 1 + (int)path_len + ((extra_len > 0) ? (1 + (int)extra_len) : (1 + 4));
476+
477+
// Payload prefix: dest_hash (1) + src_hash (1)
478+
const int prefix_len = 2;
479+
480+
// Check size based on encryption mode
481+
if (use_ascon) {
482+
// V2: prefix + counter + ciphertext + tag
483+
if (prefix_len + ASCON_COUNTER_SIZE + plain_len + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL;
484+
} else {
485+
// V1: prefix + MAC + ciphertext (rounded up to 16)
486+
int enc_len = ((plain_len + 15) / 16) * 16;
487+
if (prefix_len + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL;
488+
}
443489

444490
Packet* packet = obtainNewPacket();
445491
if (packet == NULL) {
446492
MESH_DEBUG_PRINTLN("%s Mesh::createPathReturn(): error, packet pool empty", getLogDateTime());
447493
return NULL;
448494
}
449-
packet->header = (PAYLOAD_TYPE_PATH << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
495+
496+
// Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters
497+
packet->header = (PAYLOAD_TYPE_PATH << PH_TYPE_SHIFT);
450498

451499
int len = 0;
452500
memcpy(&packet->payload[len], dest_hash, PATH_HASH_SIZE); len += PATH_HASH_SIZE; // dest hash
@@ -467,17 +515,32 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret,
467515
getRNG()->random(&data[data_len], 4); data_len += 4;
468516
}
469517

470-
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
518+
// Encrypt based on peer capability
519+
if (use_ascon) {
520+
len += Utils::encryptAscon(secret, &packet->payload[len], data, data_len);
521+
} else {
522+
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
523+
}
471524
}
472525

473526
packet->payload_len = len;
474527

475528
return packet;
476529
}
477530

478-
Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) {
531+
Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len, bool use_ascon) {
479532
if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE) {
480-
if (data_len + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL;
533+
// Payload prefix: dest_hash (1) + src_hash (1)
534+
const int prefix_len = 2;
535+
// Check size based on encryption mode
536+
if (use_ascon) {
537+
// V2: prefix + counter + ciphertext + tag
538+
if (prefix_len + (int)data_len + ASCON_COUNTER_SIZE + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL;
539+
} else {
540+
// V1: prefix + MAC + ciphertext (rounded up to 16)
541+
int enc_len = (((int)data_len + 15) / 16) * 16;
542+
if (prefix_len + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL;
543+
}
481544
} else {
482545
return NULL; // invalid type
483546
}
@@ -487,21 +550,37 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t*
487550
MESH_DEBUG_PRINTLN("%s Mesh::createDatagram(): error, packet pool empty", getLogDateTime());
488551
return NULL;
489552
}
490-
packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
553+
554+
// Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters
555+
packet->header = (type << PH_TYPE_SHIFT);
491556

492557
int len = 0;
493558
len += dest.copyHashTo(&packet->payload[len]); // dest hash
494559
len += self_id.copyHashTo(&packet->payload[len]); // src hash
495-
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
560+
561+
// Encrypt based on peer capability
562+
if (use_ascon) {
563+
len += Utils::encryptAscon(secret, &packet->payload[len], data, data_len);
564+
} else {
565+
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
566+
}
496567

497568
packet->payload_len = len;
498569

499570
return packet;
500571
}
501572

502-
Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) {
573+
Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len, bool use_ascon) {
503574
if (type == PAYLOAD_TYPE_ANON_REQ) {
504-
if (data_len + 1 + PUB_KEY_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL;
575+
// Check size based on encryption mode
576+
if (use_ascon) {
577+
// V2: dest_hash + pub_key + counter + ciphertext + tag
578+
if (data_len + 1 + PUB_KEY_SIZE + ASCON_COUNTER_SIZE + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL;
579+
} else {
580+
// V1: dest_hash + pub_key + MAC + ciphertext (rounded up to 16)
581+
int enc_len = ((data_len + 15) / 16) * 16;
582+
if (1 + PUB_KEY_SIZE + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL;
583+
}
505584
} else {
506585
return NULL; // invalid type
507586
}
@@ -511,7 +590,9 @@ Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, cons
511590
MESH_DEBUG_PRINTLN("%s Mesh::createAnonDatagram(): error, packet pool empty", getLogDateTime());
512591
return NULL;
513592
}
514-
packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
593+
594+
// Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters
595+
packet->header = (type << PH_TYPE_SHIFT);
515596

516597
int len = 0;
517598
if (type == PAYLOAD_TYPE_ANON_REQ) {
@@ -520,27 +601,52 @@ Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, cons
520601
} else {
521602
// FUTURE:
522603
}
523-
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
604+
605+
// Encrypt based on peer capability
606+
if (use_ascon) {
607+
len += Utils::encryptAscon(secret, &packet->payload[len], data, data_len);
608+
} else {
609+
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
610+
}
524611

525612
packet->payload_len = len;
526613

527614
return packet;
528615
}
529616

530-
Packet* Mesh::createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len) {
617+
Packet* Mesh::createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len, bool use_ascon) {
531618
if (!(type == PAYLOAD_TYPE_GRP_TXT || type == PAYLOAD_TYPE_GRP_DATA)) return NULL; // invalid type
532-
if (data_len + 1 + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; // too long
619+
620+
// Payload prefix: channel_hash (PATH_HASH_SIZE, currently 1)
621+
const int prefix_len = PATH_HASH_SIZE;
622+
// Check size based on encryption mode
623+
if (use_ascon) {
624+
// V2: prefix + counter + ciphertext + tag
625+
if (prefix_len + (int)data_len + ASCON_COUNTER_SIZE + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL;
626+
} else {
627+
// V1: prefix + MAC + ciphertext (rounded up to 16)
628+
int enc_len = (((int)data_len + 15) / 16) * 16;
629+
if (prefix_len + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL;
630+
}
533631

534632
Packet* packet = obtainNewPacket();
535633
if (packet == NULL) {
536634
MESH_DEBUG_PRINTLN("%s Mesh::createGroupDatagram(): error, packet pool empty", getLogDateTime());
537635
return NULL;
538636
}
539-
packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
637+
638+
// Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters
639+
packet->header = (type << PH_TYPE_SHIFT);
540640

541641
int len = 0;
542642
memcpy(&packet->payload[len], channel.hash, PATH_HASH_SIZE); len += PATH_HASH_SIZE;
543-
len += Utils::encryptThenMAC(channel.secret, &packet->payload[len], data, data_len);
643+
644+
// Encrypt based on channel flag or explicit request
645+
if (use_ascon) {
646+
len += Utils::encryptAscon(channel.secret, &packet->payload[len], data, data_len);
647+
} else {
648+
len += Utils::encryptThenMAC(channel.secret, &packet->payload[len], data, data_len);
649+
}
544650

545651
packet->payload_len = len;
546652

0 commit comments

Comments
 (0)