diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index f61f53aeed..b3b268d8af 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -274,14 +274,17 @@ File file = openRead(_getContactsChannelsFS(), "/contacts3"); bool full = false; while (!full) { ContactInfo c; + memset(&c, 0, sizeof(c)); // Zero-initialize to prevent garbage in new fields + c.shared_secret_valid = false; + c.supports_ascon = false; uint8_t pub_key[32]; - uint8_t unused; + uint8_t crypto_flags; // was 'unused'; bit 0 = supports_ascon bool success = (file.read(pub_key, 32) == 32); success = success && (file.read((uint8_t *)&c.name, 32) == 32); success = success && (file.read(&c.type, 1) == 1); success = success && (file.read(&c.flags, 1) == 1); - success = success && (file.read(&unused, 1) == 1); + success = success && (file.read(&crypto_flags, 1) == 1); success = success && (file.read((uint8_t *)&c.sync_since, 4) == 4); // was 'reserved' success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); success = success && (file.read((uint8_t *)&c.last_advert_timestamp, 4) == 4); @@ -292,6 +295,7 @@ File file = openRead(_getContactsChannelsFS(), "/contacts3"); if (!success) break; // EOF + c.supports_ascon = (crypto_flags & 0x01) != 0; // bit 0; old files have 0 here (backwards compatible) c.id = mesh::Identity(pub_key); if (!host->onContactLoaded(c)) full = true; } @@ -304,14 +308,14 @@ void DataStore::saveContacts(DataStoreHost* host) { if (file) { uint32_t idx = 0; ContactInfo c; - uint8_t unused = 0; while (host->getContactForSave(idx, c)) { + uint8_t crypto_flags = c.supports_ascon ? 0x01 : 0x00; // bit 0 = supports_ascon bool success = (file.write(c.id.pub_key, 32) == 32); success = success && (file.write((uint8_t *)&c.name, 32) == 32); success = success && (file.write(&c.type, 1) == 1); success = success && (file.write(&c.flags, 1) == 1); - success = success && (file.write(&unused, 1) == 1); + success = success && (file.write(&crypto_flags, 1) == 1); success = success && (file.write((uint8_t *)&c.sync_since, 4) == 4); success = success && (file.write((uint8_t *)&c.out_path_len, 1) == 1); success = success && (file.write((uint8_t *)&c.last_advert_timestamp, 4) == 4); @@ -335,14 +339,18 @@ void DataStore::loadChannels(DataStoreHost* host) { uint8_t channel_idx = 0; while (!full) { ChannelDetails ch; - uint8_t unused[4]; + uint8_t flags_and_unused[4]; - bool success = (file.read(unused, 4) == 4); + bool success = (file.read(flags_and_unused, 4) == 4); success = success && (file.read((uint8_t *)ch.name, 32) == 32); success = success && (file.read((uint8_t *)ch.channel.secret, 32) == 32); if (!success) break; // EOF + // First byte stores channel flags (v2 support, etc), rest unused + // Old files have all zeros here, which means v1 (backward compatible) + ch.channel.flags = flags_and_unused[0]; + if (host->onChannelLoaded(channel_idx, ch)) { channel_idx++; } else { @@ -358,11 +366,14 @@ void DataStore::saveChannels(DataStoreHost* host) { if (file) { uint8_t channel_idx = 0; ChannelDetails ch; - uint8_t unused[4]; - memset(unused, 0, 4); + uint8_t flags_and_unused[4]; while (host->getChannelForSave(channel_idx, ch)) { - bool success = (file.write(unused, 4) == 4); + // First byte stores channel flags (v2 support, etc), rest unused + memset(flags_and_unused, 0, 4); + flags_and_unused[0] = ch.channel.flags; + + bool success = (file.write(flags_and_unused, 4) == 4); success = success && (file.write((uint8_t *)ch.name, 32) == 32); success = success && (file.write((uint8_t *)ch.channel.secret, 32) == 32); diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 1e4115dada..a2a4155650 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -56,6 +56,7 @@ #define CMD_SEND_ANON_REQ 57 #define CMD_SET_AUTOADD_CONFIG 58 #define CMD_GET_AUTOADD_CONFIG 59 +#define CMD_GET_CHANNEL_V2 60 // returns 51 bytes (includes flags) // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -88,6 +89,7 @@ #define RESP_CODE_TUNING_PARAMS 23 #define RESP_CODE_STATS 24 // v8+, second byte is stats type #define RESP_CODE_AUTOADD_CONFIG 25 +#define RESP_CODE_CHANNEL_INFO_V2 26 // reply to CMD_GET_CHANNEL_V2 (51 bytes) #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -1109,7 +1111,7 @@ void MyMesh::handleCmdFrame(size_t len) { dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { - ContactInfo contact; + ContactInfo contact = {}; // Zero-initialize to prevent garbage in supports_ascon etc. updateContactFromFrame(contact, last_mod, cmd_frame, len); contact.lastmod = last_mod; contact.sync_since = 0; @@ -1500,6 +1502,23 @@ void MyMesh::handleCmdFrame(size_t len) { i += 32; memcpy(&out_frame[i], channel.channel.secret, 16); i += 16; // NOTE: only 128-bit supported + // flags byte NOT included - old apps expect exactly 50 bytes + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_NOT_FOUND); + } + } else if (cmd_frame[0] == CMD_GET_CHANNEL_V2 && len >= 2) { + uint8_t channel_idx = cmd_frame[1]; + ChannelDetails channel; + if (getChannel(channel_idx, channel)) { + int i = 0; + out_frame[i++] = RESP_CODE_CHANNEL_INFO_V2; + out_frame[i++] = channel_idx; + strcpy((char *)&out_frame[i], channel.name); + i += 32; + memcpy(&out_frame[i], channel.channel.secret, 16); + i += 16; // NOTE: only 128-bit supported + out_frame[i++] = channel.channel.flags; // V2: includes flags byte (51 bytes total) _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_NOT_FOUND); @@ -1509,9 +1528,13 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16) { uint8_t channel_idx = cmd_frame[1]; ChannelDetails channel; + memset(&channel, 0, sizeof(channel)); StrHelper::strncpy(channel.name, (char *)&cmd_frame[2], 32); - memset(channel.channel.secret, 0, sizeof(channel.channel.secret)); memcpy(channel.channel.secret, &cmd_frame[2 + 32], 16); // NOTE: only 128-bit supported + // Optional flags byte: old apps don't send it (flags defaults to 0 = AES) + if (len >= 2 + 32 + 16 + 1) { + channel.channel.flags = cmd_frame[2 + 32 + 16]; + } if (setChannel(channel_idx, channel)) { saveChannels(); writeOKFrame(); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6d957cc094..660577ea21 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -506,7 +506,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, - uint8_t *data, size_t len) { + uint8_t *data, size_t len, bool was_ascon) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin // client (unknown at this stage) uint32_t timestamp; @@ -530,16 +530,17 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m if (reply_len == 0) return; // invalid request + // Reply with same encryption the sender used if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, was_ascon); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else if (reply_path_len < 0) { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len, was_ascon); if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len, was_ascon); if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); } } @@ -565,6 +566,17 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } } +void MyMesh::onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) { + int i = matching_peer_indexes[peer_idx]; + if (i >= 0 && i < acl.getNumClients()) { + auto client = acl.getClientByIdx(i); + if (supports_ascon && !client->supports_ascon) { + client->supports_ascon = true; + MESH_DEBUG_PRINTLN("Auto-detected Ascon capability for client %d", i); + } + } +} + static bool isShare(const mesh::Packet *packet) { if (packet->hasTransportCodes()) { return packet->transport_codes[0] == 0 && packet->transport_codes[1] == 0; // codes { 0, 0 } means 'send to nowhere' @@ -576,11 +588,19 @@ void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32 const uint8_t *app_data, size_t app_data_len) { mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl - // if this a zero hop advert (and not via 'Share'), add it to neighbours - if (packet->path_len == 0 && !isShare(packet)) { - AdvertDataParser parser(app_data, app_data_len); - if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters - putNeighbour(id, timestamp, packet->getSNR()); + AdvertDataParser parser(app_data, app_data_len); + if (parser.isValid()) { + // Update Ascon encryption capability for known clients (chat nodes that are in our ACL) + ClientInfo* client = acl.getClient(id.pub_key, PUB_KEY_SIZE); + if (client) { + client->supports_ascon = (parser.getFeat1() & ADV_FEAT1_ASCON_CAPABLE) != 0; + } + + // if this a zero hop advert (and not via 'Share'), add it to neighbours + if (packet->path_len == 0 && !isShare(packet)) { + if (parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters + putNeighbour(id, timestamp, packet->getSNR()); + } } } } @@ -608,11 +628,11 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, client->supports_ascon); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { mesh::Packet *reply = - createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len, client->supports_ascon); if (reply) { if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); @@ -673,7 +693,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len, client->supports_ascon); if (reply) { if (client->out_path_len < 0) { sendFlood(reply, CLI_REPLY_DELAY_MILLIS); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 0d5cd28a3d..85273d7661 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -160,10 +160,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool filterRecvFloodPacket(mesh::Packet* pkt) override; - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, bool was_ascon) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; - void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len); + void onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) override; + void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override; void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; 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; void onControlDataRecv(mesh::Packet* packet) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 22a3d208b5..4935240e57 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -71,7 +71,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { mesh::Utils::sha256((uint8_t *)&client->extra.room.pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE); client->extra.room.push_post_timestamp = post.post_timestamp; - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len, client->supports_ascon); if (reply) { if (client->out_path_len < 0) { sendFlood(reply); @@ -279,7 +279,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, - uint8_t *data, size_t len) { + uint8_t *data, size_t len, bool was_ascon) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin // client (unknown at this stage) uint32_t sender_timestamp, sender_sync_since; @@ -349,13 +349,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first + // Reply with same encryption the sender used if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, 13); + PAYLOAD_TYPE_RESPONSE, reply_data, 13, was_ascon); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13); + mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13, was_ascon); if (reply) { if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); @@ -387,6 +388,31 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } } +void MyMesh::onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) { + int i = matching_peer_indexes[peer_idx]; + if (i >= 0 && i < acl.getNumClients()) { + auto client = acl.getClientByIdx(i); + if (supports_ascon && !client->supports_ascon) { + client->supports_ascon = true; + MESH_DEBUG_PRINTLN("Auto-detected Ascon capability for client %d", i); + } + } +} + +void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp, + const uint8_t *app_data, size_t app_data_len) { + mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl + + // Update Ascon encryption capability for known clients + AdvertDataParser parser(app_data, app_data_len); + if (parser.isValid()) { + ClientInfo* client = acl.getClient(id.pub_key, PUB_KEY_SIZE); + if (client) { + client->supports_ascon = (parser.getFeat1() & ADV_FEAT1_ASCON_CAPABLE) != 0; + } + } +} + void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, uint8_t *data, size_t len) { int i = matching_peer_indexes[sender_idx]; @@ -480,7 +506,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, // PUB_KEY_SIZE); - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len, client->supports_ascon); if (reply) { if (client->out_path_len < 0) { sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY); @@ -537,10 +563,10 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, client->supports_ascon); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len, client->supports_ascon); if (reply) { if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index f470e55eb8..d4d5709878 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -145,9 +145,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } bool allowPacketForward(const mesh::Packet* packet) override; - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, bool was_ascon) override; int searchPeersByHash(const uint8_t* hash) override ; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; + void onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) override; + void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override; void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; 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; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 018ec2a20f..fd5194bb15 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -408,7 +408,9 @@ class MyMesh : public BaseChatMesh, ContactVisitor { temp[5 + MAX_TEXT_LEN] = 0; // truncate if too long int len = strlen((char *) &temp[5]); - auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, _public->channel, temp, 5 + len); + // Use Ascon if channel has the flag set, otherwise use legacy for backwards compatibility + bool use_ascon = (_public->channel.flags & CHANNEL_FLAG_ASCON) != 0; + auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, _public->channel, temp, 5 + len, use_ascon); if (pkt) { sendFlood(pkt); Serial.println(" Sent."); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 8e27323edd..716098b210 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -256,7 +256,7 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { mesh::Utils::sha256((uint8_t *)&t->expected_acks[t->attempt], 4, data, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); t->attempt++; - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len); + auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len, c->supports_ascon); if (pkt) { if (c->out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(pkt, c->out_path, c->out_path_len); @@ -447,7 +447,7 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r } } -void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) { +void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, bool was_ascon) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) uint32_t timestamp; memcpy(×tamp, data, 4); @@ -464,13 +464,14 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con if (reply_len == 0) return; // invalid request + // Reply with same encryption the sender used if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, was_ascon); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len, was_ascon); if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); } } @@ -496,6 +497,31 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } } +void SensorMesh::onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) { + int i = matching_peer_indexes[peer_idx]; + if (i >= 0 && i < acl.getNumClients()) { + auto client = acl.getClientByIdx(i); + if (supports_ascon && !client->supports_ascon) { + client->supports_ascon = true; + MESH_DEBUG_PRINTLN("Auto-detected Ascon capability for client %d", i); + } + } +} + +void SensorMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp, + const uint8_t *app_data, size_t app_data_len) { + mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl + + // Update Ascon encryption capability for known clients + AdvertDataParser parser(app_data, app_data_len); + if (parser.isValid()) { + ClientInfo* client = acl.getClient(id.pub_key, PUB_KEY_SIZE); + if (client) { + client->supports_ascon = (parser.getFeat1() & ADV_FEAT1_ASCON_CAPABLE) != 0; + } + } +} + void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash) { if (dest.out_path_len < 0) { mesh::Packet* ack = createAck(ack_hash); @@ -536,10 +562,10 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, from->supports_ascon); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len); + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len, from->supports_ascon); if (reply) { if (from->out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, from->out_path, from->out_path_len, SERVER_RESPONSE_DELAY); @@ -566,13 +592,13 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); + PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4, from->supports_ascon); if (path) sendFlood(path, TXT_ACK_DELAY); } else { sendAckTo(*from, ack_hash); } } - } else if (flags == TXT_TYPE_CLI_DATA) { + } else if (flags == TXT_TYPE_CLI_DATA) { from->last_timestamp = sender_timestamp; from->last_activity = getRTCClock()->getCurrentTime(); @@ -594,7 +620,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = (TXT_TYPE_CLI_DATA << 2); - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len); + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len, from->supports_ascon); if (reply) { if (from->out_path_len < 0) { sendFlood(reply, CLI_REPLY_DELAY_MILLIS); diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index ed35234582..7153b34a0b 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -120,9 +120,11 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override; int getInterferenceThreshold() const override; int getAGCResetInterval() const override; - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len, bool was_ascon) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; + void onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) override; + void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override; void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; 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; void onControlDataRecv(mesh::Packet* packet) override; diff --git a/lib/LibAscon/include/ascon.h b/lib/LibAscon/include/ascon.h new file mode 100644 index 0000000000..8261ebe958 --- /dev/null +++ b/lib/LibAscon/include/ascon.h @@ -0,0 +1,2131 @@ +/** + * @file + * Ascon cipher - Lightweight Authenticated Encryption & Hashing, + * also with Init-Update-Final paradigm. + * + * Ascon is a family of authenticated encryption and hashing algorithms + * designed to be lightweight and easy to implement, even with added + * countermeasures against side-channel attacks. + * + * For more information on the Ascon cipher itself, visit + * https://ascon.iaik.tugraz.at/ + * + * This file is the interface to the Ascon library providing: + * - the Ascon symmetric AEAD ciphers Ascon128, Ascon128a, Ascon80pq + * - the Ascon fixed-size output hash and variable-size output hash (XOF) + * + * All functionalities are available in: + * - online form (init-update-final paradigm): the data is processed one + * chunk at the time; useful if it's still being received or does not + * fit into memory + * - offline form: the data is available as a whole in memory and processed + * in one go + * + * Library dependencies: + * - only the C99 or C11 standard library, as seen in the `#include` statements + * of this file + * + * @license Creative Commons Zero (CC0) 1.0 + * @authors See AUTHORS.md file + */ + +#ifndef ASCON_H +#define ASCON_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include /* For uint8_t, uint_fast8_t, uint64_t */ +#include /* For size_t, NULL */ +#include /* For bool, true, false */ + +#if defined(ASCON_INPUT_ASSERTS) && !defined(ASCON_ASSERT) +/** + * @def ASCON_INPUT_ASSERTS + * When defined, enables the runtime assertions on the parameters of all + * functions of the library API using #ASCON_ASSERT. + * + * The check is mostly against NULL pointers, for the correct order of calling + * of the many Init-Update-Final functions and against mixing functions from + * different AEAD algorithms (128 vs 128a vs 80pq). It's recommended to + * use it in debug mode, optionally also in release mode. + * + * If #ASCON_INPUT_ASSERTS is defined, the user can also pre-define + * #ASCON_ASSERT to any custom assertion macro or function. + * + * @see #ASCON_ASSERT + */ +// Redefining ASCON_INPUT_ASSERTS otherwise Doxygen does not find it +#undef ASCON_INPUT_ASSERTS +#define ASCON_INPUT_ASSERTS 1 +/** + * @def ASCON_ASSERT + * Assertion macro, used to stop execution when a critical error is + * encountered. + * + * - Equal to `assert` from `assert.h` if #ASCON_INPUT_ASSERTS is defined. + * - Can also be pre-defined by the user to any custom assertion. + * - Otherwise does nothing. + */ +#include /* For assert() */ +#define ASCON_ASSERT(expr) assert(expr) +#elif !defined(ASCON_ASSERT) +// Neither ASCON_INPUT_ASSERTS nor ASCON_ASSERT are defined, +// so make the assert macro do nothing. +#define ASCON_ASSERT(expr) +#endif + +/** + * @def ASCON_API + * Marker of all the library's public API functions. Used to add exporting + * indicators for DLL on Windows, empty on other platforms. + */ +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(__NT__) +/** + * @def ASCON_WINDOWS + * Indicator simplifying the check for the Windows platform (undefined on other platforms). + * Used for internal decisions on how to inline functions. + */ +#define ASCON_WINDOWS 1 +#define ASCON_API __declspec(dllexport) +#else +#define ASCON_API +#endif + +/** Major version of this API conforming to semantic versioning. */ +#define ASCON_API_VERSION_MAJOR 1 +/** Minor version of this API conforming to semantic versioning. */ +#define ASCON_API_VERSION_MINOR 2 +/** Bugfix/patch version of this API conforming to semantic versioning. */ +#define ASCON_API_VERSION_BUGFIX 1 +/** Version of this API conforming to semantic versioning as a string. */ +#define ASCON_API_VERSION "1.2.1" + +/** + * Length in bytes of the secret symmetric key used for the Ascon128 cipher. + */ +#define ASCON_AEAD128_KEY_LEN 16U + +/** + * Length in bytes of the secret symmetric key used for the Ascon128a cipher. + */ +#define ASCON_AEAD128a_KEY_LEN 16U + +/** + * Length in bytes of the secret symmetric key used for the Ascon80pq cipher. + */ +#define ASCON_AEAD80pq_KEY_LEN 20U + +/** + * Length in bytes of the public nonce used for authenticated + * encryption and decryption. + */ +#define ASCON_AEAD_NONCE_LEN 16U + +/** + * Minimum recommended length in bytes of the authentication tag generated by + * the authenticated encryption and validated by the decryption. + */ +#define ASCON_AEAD_TAG_MIN_SECURE_LEN 16U + +/** + * Length in bytes of the digest generated by the fixed-size (non-xof) hash + * function. + */ +#define ASCON_HASH_DIGEST_LEN 32U + +/** + * Length in bytes of the digest generated by the fixed-size (non-xof) hasha + * function. + */ +#define ASCON_HASHA_DIGEST_LEN ASCON_HASH_DIGEST_LEN + +/** + * Number of bytes the cipher can process at the time in AEAD128 mode and + * hashing. + */ +#define ASCON_RATE 8U + +/** + * Number of bytes the cipher can process at the time in AEAD128a mode. + * + * The AEAD128a cipher absorbs blocks twice the standard rate size. + */ +#define ASCON_DOUBLE_RATE (2U * ASCON_RATE) + +/** + * The tag is valid thus the associated data and ciphertext were intact. + */ +#define ASCON_TAG_OK true +/** + * The tag is invalid thus the associated data and decrypted data should be + * ignored. + */ +#define ASCON_TAG_INVALID false + +/** + * Internal cipher sponge state (320 bits). + */ +typedef struct ascon_sponge +{ + /** Sponge's first field */ + uint64_t x0; + /** Sponge's second field */ + uint64_t x1; + /** Sponge's third field */ + uint64_t x2; + /** Sponge's fourth field */ + uint64_t x3; + /** Sponge's fifth field */ + uint64_t x4; +} ascon_sponge_t; + +/** + * Internal cipher sponge state associated with a buffer holding for + * less-than-rate updates. Used for the Init-Update-Final implementation. + */ +typedef struct ascon_bufstate +{ + /** Cipher sponge state. */ + ascon_sponge_t sponge; + + /** Buffer caching the less-than-rate long input between update calls. */ + uint8_t buffer[ASCON_DOUBLE_RATE]; + + /** Currently used bytes of the buffer. */ + uint8_t buffer_len; + + /** + * State of the order of Init-Update-Final function calls, checked to + * know when to finalise the associated data processing and for the + * runtime assertions on the correct order of the functions. + * + * @see #ASCON_INPUT_ASSERTS + * + * Note: this variable is not semantically relevant in THIS struct, + * as it should belong in the struct ascon_aead_ctx_t, but by having it + * here we spare bytes of padding (7 on 64-bit systems, 3 on 32-bit, + * 1 on 16-bit) at the end of the struct ascon_aead_ctx_t, by using some + * of the padding space this struct anyway has. + */ + uint8_t flow_state; + + /** Unused padding to the next uint64_t (sponge.x0 or ctx.k0) + * to avoid errors when compiling with `-Wpadding` on any platform. */ + uint8_t pad[6]; +} ascon_bufstate_t; + +/** Cipher context for hashing. */ +typedef ascon_bufstate_t ascon_hash_ctx_t; + +/** + * Cipher context for authenticated encryption and validated decryption. + * + * Half of this context's size is the cipher's sponge state, the remaining + * part is holding the key and the buffering of online data (and some padding). + */ +typedef struct ascon_aead_ctx +{ + /** Cipher buffered sponge state. */ + ascon_bufstate_t bufstate; + + /** Copy of the secret key, to be used in the final step, first part. */ + uint64_t k0; + + /** Copy of the secret key, to be used in the final step, second part. */ + uint64_t k1; + + /** Copy of the secret key, to be used in the final step, third part, + * used only in the Ascon80pq cipher. */ + uint64_t k2; +} ascon_aead_ctx_t; + +/** + * Offline symmetric encryption using Ascon128. + * + * Encrypts the data which is already available as a whole in a contiguous + * buffer, authenticating any optional associated data in the process. + * Provides the ciphertext and the authentication tag as output. + * + * In case of no associated data at all to be authenticated, set + * \p assoc_data_len to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * @image html encrypt.png + * + * @warning + * The nonce **must be unique**, as the strength of the AEAD is based on + * its uniqueness. + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[out] ciphertext encrypted data with the same length as the + * plaintext, thus \p plaintext_len will be written in this buffer. + * This pointer may also point to the same location as \p plaintext + * to encrypt the plaintext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[out] tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] key secret key of #ASCON_AEAD128_KEY_LEN. Not NULL. + * @param[in] nonce public **unique** nonce of #ASCON_AEAD_NONCE_LEN bytes. + * @param[in] assoc_data data to be authenticated with the same tag + * but not encrypted. Can be NULL iff \p assoc_data_len is 0. + * @param[in] plaintext data to be encrypted into \p ciphertext. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. Can be 0. + * @param[in] plaintext_len length of the data pointed by \p plaintext in + * bytes. Can be 0 (not recommended, see warning). + * @param[in] tag_len length of the tag to generate in bytes. At least + * #ASCON_AEAD_TAG_MIN_SECURE_LEN is recommended for security. + */ +ASCON_API void +ascon_aead128_encrypt(uint8_t* ciphertext, + uint8_t* tag, + const uint8_t key[ASCON_AEAD128_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* plaintext, + size_t assoc_data_len, + size_t plaintext_len, + size_t tag_len); + +/** + * Online symmetric encryption/decryption using Ascon128, initialisation. + * + * Prepares to start a new encryption or decryption session for plaintext or + * ciphertext and associated data being provided one chunk at the time. + * + * The key and nonce are copied/absorbed into the internal state, so they can + * be deleted from their original location after this function returns. + * + * The calling order for encryption/decryption is: + * 1. ascon_aead128_init() - once only + * 2. ascon_aead128_assoc_data_update() - 0 or more times + * 3. ascon_aead128_encrypt_update() / ascon_aead128_decrypt_update() - 0 or + * more times, see warning + * 4. ascon_aead128_encrypt_final() / ascon_aead128_encrypt_final() - once only + * + * @image html encrypt.png + * @image html decrypt.png + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during the ascon_aead128_encrypt_final() or ascon_aead128_decrypt_final() + * call. In case the encryption or decryption session is interrupted and never + * finalised, clear the context with ascon_aead_cleanup() to erase the key copy. + * + * @warning + * Do not mix Init-Update-Final functions across ciphers. + * + * @param[in, out] ctx the encryption/decryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[in] key secret key of #ASCON_AEAD128_KEY_LEN bytes. Not NULL + * @param[in] nonce public unique nonce of #ASCON_AEAD_NONCE_LEN bytes. Not + * NULL. + */ +ASCON_API void +ascon_aead128_init(ascon_aead_ctx_t* ctx, + const uint8_t key[ASCON_AEAD128_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN]); + +/** + * Online symmetric encryption/decryption using Ascon128, feeding associated + * data. + * + * Feeds a chunk of associated data to the already initialised encryption + * or decryption session. The data will be authenticated by the tag provided by + * the final function, but not encrypted or decrypted. + * + * In case of no associated data at all to be authenticated/validated, this + * function can either be skipped completely or called (also many times) + * with \p assoc_data_len set to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * After calling ascon_aead128_encrypt_update() or + * ascon_aead128_decrypt_update(), this function **must** not be used anymore + * on the same context. + * + * The calling order for encryption/decryption is: + * 1. ascon_aead128_init() - once only + * 2. ascon_aead128_assoc_data_update() - 0 or more times + * 3. ascon_aead128_encrypt_update() / ascon_aead128_decrypt_update() - 0 or + * more times, see warning + * 4. ascon_aead128_encrypt_final() / ascon_aead128_encrypt_final() - once only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[in, out] ctx the encryption/decryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[in] assoc_data data to be authenticated/validated with the same tag + * but not encrypted/decrypted. May be NULL iff \p assoc_data_len is 0. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. May be 0. + */ +ASCON_API void +ascon_aead128_assoc_data_update(ascon_aead_ctx_t* ctx, + const uint8_t* assoc_data, + size_t assoc_data_len); + +/** + * Online symmetric encryption using Ascon128, feeding plaintext and getting + * ciphertext. + * + * Feeds a chunk of plaintext data to the encryption session after any + * optional associated data has been processed. The plaintext will be encrypted + * and provided as ciphertext in buffered chunks of #ASCON_RATE bytes. + * + * It will automatically finalise the absorption of any associated data, + * so no new associated data could be processed after this function is called. + * + * The calling order for encryption is: + * 1. ascon_aead128_init() - once only + * 2. ascon_aead128_assoc_data_update() - 0 or more times + * 3. ascon_aead128_encrypt_update() - 0 or more times, see warning + * 4. ascon_aead128_encrypt_final() - once only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[in, out] ctx the encryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[out] ciphertext encrypted data, buffered into chunks. + * This function will write a multiple of #ASCON_RATE bytes in the + * interval [0, \p plaintext_len + #ASCON_RATE[ into \p ciphertext. + * The exact number of written bytes is indicated by the return value. + * This pointer may also point to the same location as \p plaintext + * to encrypt the plaintext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] plaintext data to be encrypted into \p ciphertext. All of the + * plaintext will be processed, even if the function provides less than + * \p plaintext_len output bytes. They are just buffered. Not NULL. + * @param[in] plaintext_len length of the data pointed by \p plaintext in + * bytes. May be 0. + * @returns number of bytes written into \p ciphertext. The value is a multiple + * of #ASCON_RATE in [0, \p plaintext_len + #ASCON_RATE[. + */ +ASCON_API size_t +ascon_aead128_encrypt_update(ascon_aead_ctx_t* ctx, + uint8_t* ciphertext, + const uint8_t* plaintext, + size_t plaintext_len); + +/** + * Online symmetric encryption using Ascon128, finalisation and tag generation. + * + * Finalises the authenticated encryption by returning any remaining buffered + * ciphertext and the authentication tag. The total ciphertext length is + * equal to the total plaintext length. + * + * It will securely erase the content of the \p ctx struct before returning. + * + * The calling order for encryption is: + * 1. ascon_aead128_init() - once only + * 2. ascon_aead128_assoc_data_update() - 0 or more times + * 3. ascon_aead128_encrypt_update() - 0 or more times, see warning + * 4. ascon_aead128_encrypt_final() - once only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during this function call. In case the encryption session is interrupted + * and never finalised (this function is never called), clear the context with + * ascon_aead_cleanup() to erase the key copy. + * + * @param[in, out] ctx the encryption context, handling the cipher + * state and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] ciphertext trailing encrypted data still available in the + * buffer of the buffered updating. This function will write + * [0, #ASCON_RATE[ bytes into \p ciphertext. + * The exact number of written bytes is indicated by the return value. + * Not NULL. + * @param[out] tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] tag_len length of the tag to generate in bytes. At least + * #ASCON_AEAD_TAG_MIN_SECURE_LEN is recommended for security. + * @returns number of bytes written into \p ciphertext. The value is in the + * interval [0, #ASCON_RATE[, i.e. whatever remained in the buffer + * after the last update call. + */ +ASCON_API size_t +ascon_aead128_encrypt_final(ascon_aead_ctx_t* ctx, + uint8_t* ciphertext, + uint8_t* tag, + size_t tag_len); + +/** + * Offline symmetric decryption using Ascon128. + * + * Decrypts the data which is already available as a whole in a contiguous + * buffer, validating any optional associated data in the process. + * Provides the plaintext and the validity of the authentication tag as output. + * The total plaintext length is equal to the total ciphertext length. + * + * In case of no associated data at all to be authenticated, set + * \p assoc_data_len to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * @image html decrypt.png + * + * @param[out] plaintext decrypted data with the same length as the + * ciphertext, thus \p ciphertext_len will be written in this buffer. + * This pointer may also point to the same location as \p ciphertext + * to decrypt the ciphertext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] key secret key of #ASCON_AEAD128_KEY_LEN bytes. Not NULL. + * @param[in] nonce public unique nonce of #ASCON_AEAD_NONCE_LEN bytes. + * @param[in] assoc_data data to be validated with the same tag + * but not decrypted. Can be NULL iff \p assoc_data_len is 0. + * @param[in] ciphertext data to be decrypted into \p plaintext. + * @param[in] expected_tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. Can be 0. + * @param[in] ciphertext_len length of the data pointed by \p ciphertext in + * bytes. Can be 0 (not recommended, see warning of + * ascon_aead128_encrypt()). + * @param[in] expected_tag_len length of the \p tag to check in bytes. It should be + * the same length as generated during the encryption + * but it can be shorter (although it's not recommended). + * @returns the answer to the question "is tha tag valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the tag is correct, + * thus the associated data and ciphertext are intact and authentic. + * `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_aead128_decrypt(uint8_t* plaintext, + const uint8_t key[ASCON_AEAD128_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* ciphertext, + const uint8_t* expected_tag, + size_t assoc_data_len, + size_t ciphertext_len, + size_t expected_tag_len); + +/** + * Online symmetric decryption using Ascon128, feeding ciphertext and getting + * plaintext. + * + * Feeds a chunk of ciphertext data to the decryption session after any + * optional associated data has been processed. The ciphertext will be decrypted + * and provided back in buffered chunks of #ASCON_RATE bytes. + * + * It will automatically finalise the absorption of any associated data, + * so no new associated data could be processed after this function is called. + * + * The calling order for decryption is: + * 1. ascon_aead128_init() - once only + * 2. ascon_aead128_assoc_data_update() - 0 or more times + * 3. ascon_aead128_decrypt_update() - 0 or more times, see warning + * 4. ascon_aead128_decrypt_final() - once only + * + * @param[in, out] ctx the decryption context, handling the cipher state + * and buffering of incoming data to be processed. Not NULL. + * @param[out] plaintext decrypted data, buffered into chunks. + * This function will write a multiple of #ASCON_RATE bytes in the + * interval [0, \p ciphertext_len + #ASCON_RATE[ into \p plaintext. + * The exact number of written bytes is indicated by the return value. + * This pointer may also point to the same location as \p ciphertext + * to decrypt the ciphertext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] ciphertext data to be decrypted into \p plaintext. All of the + * ciphertext will be processed, even if the function provides less than + * \p ciphertext_len output bytes. They are just buffered. Not NULL. + * @param[in] ciphertext_len length of the data pointed by \p ciphertext in + * bytes. May be 0. + * @returns number of bytes written into \p plaintext. The value is a multiple + * of #ASCON_RATE in [0, \p ciphertext_len + #ASCON_RATE[. + */ +ASCON_API size_t +ascon_aead128_decrypt_update(ascon_aead_ctx_t* ctx, + uint8_t* plaintext, + const uint8_t* ciphertext, + size_t ciphertext_len); + +/** + * Online symmetric decryption using Ascon128, finalisation and tag validation. + * + * Finalises the authenticated decryption by returning any remaining buffered + * plaintext and the validity of the authentication tag. + * The total plaintext length is equal to the total ciphertext length. + * + * It will securely erase the content of the \p ctx struct before returning. + * + * The calling order for decryption is: + * 1. ascon_aead128_init() - once only + * 2. ascon_aead128_assoc_data_update() - 0 or more times + * 3. ascon_aead128_decrypt_update() - 0 or more times, see warning + * 4. ascon_aead128_decrypt_final() - once only + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during this function call. In case the decryption session is interrupted + * and never finalised (this function is never called), clear the context with + * ascon_aead_cleanup() to erase the key copy. + * + * @param[in, out] ctx the decryption context, handling the cipher + * state and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] plaintext trailing decrypted data still available in the + * buffer of the buffered updating. This function will write + * [0, #ASCON_RATE[ bytes into \p plaintext. + * The exact number of written bytes is indicated by the return value. + * Not NULL. + * @param[out] is_tag_valid the answer to the question "is the tag valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the tag is correct, + * thus the associated data and ciphertext are intact and authentic. + * `false` (== #ASCON_TAG_INVALID) otherwise. + * @param[in] expected_tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] expected_tag_len length of the \p tag to check in bytes. It should be + * the same length as generated during the encryption + * but it can be shorter (although it's not recommended). + * @returns number of bytes written into \p plaintext. The value is in the + * interval [0, #ASCON_RATE[, i.e. whatever remained in the buffer + * after the last update call. + */ +ASCON_API size_t +ascon_aead128_decrypt_final(ascon_aead_ctx_t* ctx, + uint8_t* plaintext, + bool* is_tag_valid, + const uint8_t* expected_tag, + size_t expected_tag_len); + +/** + * Security cleanup of the AEAD context, in case the online processing + * is not completed to the end. + * + * Use this function only when something goes wrong between the calls of + * online encryption or decryption and you never call the + * `ascon_aead*_encrypt_final()` or `ascon_aead*_decrypt_final()` functions + * of the cipher you are currently using (because these 2 functions perform the + * cleanup automatically). + * + * This is to prevent any information (especially the key!) to leak through the + * context in case an encryption/decryption transaction is rolled back/abruptly + * terminated. + * + * @param[in, out] ctx to erase. + */ +ASCON_API void +ascon_aead_cleanup(ascon_aead_ctx_t* ctx); + +/** + * Offline symmetric encryption using Ascon128a, which uses a double data rate + * compared to Ascon128. + * + * Encrypts the data which is already available as a whole in a contiguous + * buffer, authenticating any optional associated data in the process. + * Provides the ciphertext and the authentication tag as output. + * + * In case of no associated data at all to be authenticated, set + * \p assoc_data_len to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * @image html encrypt.png + * + * @warning + * The nonce **must be unique**, as the strength of the AEAD is based on + * its uniqueness. + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[out] ciphertext encrypted data with the same length as the + * plaintext, thus \p plaintext_len will be written in this buffer. + * This pointer may also point to the same location as \p plaintext + * to encrypt the plaintext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[out] tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] key secret key of #ASCON_AEAD128a_KEY_LEN bytes. Not NULL. + * @param[in] nonce public **unique** nonce of #ASCON_AEAD_NONCE_LEN bytes. + * @param[in] assoc_data data to be authenticated with the same tag + * but not encrypted. Can be NULL iff \p assoc_data_len is 0. + * @param[in] plaintext data to be encrypted into \p ciphertext. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. Can be 0. + * @param[in] plaintext_len length of the data pointed by \p plaintext in + * bytes. Can be 0 (not recommended, see warning). + * @param[in] tag_len length of the tag to generate in bytes. At least + * #ASCON_AEAD_TAG_MIN_SECURE_LEN is recommended for security. +*/ +ASCON_API void +ascon_aead128a_encrypt(uint8_t* ciphertext, + uint8_t* tag, + const uint8_t key[ASCON_AEAD128_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* plaintext, + size_t assoc_data_len, + size_t plaintext_len, + size_t tag_len); + + +/** + * Online symmetric encryption/decryption using Ascon128a, initialisation. + * + * Prepares to start a new encryption or decryption session for plaintext or + * ciphertext and associated data being provided one chunk at the time. + * + * The key and nonce are copied/absorbed into the internal state, so they can + * be deleted from their original location after this function returns. + * + * The calling order for encryption/decryption is: + * 1. ascon_aead128a_init() - once only + * 2. ascon_aead128a_assoc_data_update() - 0 or more times + * 3. ascon_aead128a_encrypt_update() / ascon_aead128a_decrypt_update() - 0 or + * more times, see warning + * 4. ascon_aead128a_encrypt_final() / ascon_aead128a_encrypt_final() - once + * only + * + * @image html encrypt.png + * @image html decrypt.png + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during the ascon_aead128a_encrypt_final() or ascon_aead128a_decrypt_final() + * call. In case the encryption or decryption session is interrupted and never + * finalised, clear the context with ascon_aead_cleanup() to erase the key copy. + * + * @warning + * Do not mix Init-Update-Final functions across ciphers. + * + * @param[in, out] ctx the encryption/decryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[in] key secret key of #ASCON_AEAD128a_KEY_LEN bytes. Not NULL + * @param[in] nonce public unique nonce of #ASCON_AEAD_NONCE_LEN bytes. Not + * NULL. + */ +ASCON_API void +ascon_aead128a_init(ascon_aead_ctx_t* ctx, + const uint8_t key[ASCON_AEAD128a_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN]); + +/** + * Online symmetric encryption/decryption using Ascon128a, feeding associated + * data. + * + * Feeds a chunk of associated data to the already initialised encryption + * or decryption session. The data will be authenticated by the tag provided by + * the final function, but not encrypted or decrypted. + * + * In case of no associated data at all to be authenticated/validated, this + * function can either be skipped completely or called (also many times) + * with \p assoc_data_len set to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * After calling ascon_aead128a_encrypt_update() or + * ascon_aead128a_decrypt_update(), this function **must** not be used anymore + * on the same context. + * + * The calling order for encryption/decryption is: + * 1. ascon_aead128a_init() - once only + * 2. ascon_aead128a_assoc_data_update() - 0 or more times + * 3. ascon_aead128a_encrypt_update() / ascon_aead128a_decrypt_update() - 0 or + * more times, see warning + * 4. ascon_aead128a_encrypt_final() / ascon_aead128a_encrypt_final() - once + * only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[in, out] ctx the encryption/decryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[in] assoc_data data to be authenticated/validated with the same tag + * but not encrypted/decrypted. May be NULL iff \p assoc_data_len is 0. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. May be 0. + */ +ASCON_API void +ascon_aead128a_assoc_data_update(ascon_aead_ctx_t* ctx, + const uint8_t* assoc_data, + size_t assoc_data_len); + +/** + * Online symmetric encryption using Ascon128a, feeding plaintext and getting + * ciphertext. + * + * Feeds a chunk of plaintext data to the encryption session after any + * optional associated data has been processed. The plaintext will be encrypted + * and provided as ciphertext in buffered chunks of #ASCON_DOUBLE_RATE bytes. + * + * It will automatically finalise the absorption of any associated data, + * so no new associated data could be processed after this function is called. + * + * The calling order for encryption is: + * 1. ascon_aead128a_init() - once only + * 2. ascon_aead128a_assoc_data_update() - 0 or more times + * 3. ascon_aead128a_encrypt_update() - 0 or more times, see warning + * 4. ascon_aead128a_encrypt_final() - once only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[in, out] ctx the encryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[out] ciphertext encrypted data, buffered into chunks. + * This function will write a multiple of #ASCON_DOUBLE_RATE bytes in the + * interval [0, \p plaintext_len + #ASCON_DOUBLE_RATE[ into \p ciphertext. + * The exact number of written bytes is indicated by the return value. + * This pointer may also point to the same location as \p plaintext + * to encrypt the plaintext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] plaintext data to be encrypted into \p ciphertext. All of the + * plaintext will be processed, even if the function provides less than + * \p plaintext_len output bytes. They are just buffered. Not NULL. + * @param[in] plaintext_len length of the data pointed by \p plaintext in + * bytes. May be 0. + * @returns number of bytes written into \p ciphertext. The value is a multiple + * of #ASCON_RATE in [0, \p plaintext_len + #ASCON_DOUBLE_RATE[. + */ +ASCON_API size_t +ascon_aead128a_encrypt_update(ascon_aead_ctx_t* ctx, + uint8_t* ciphertext, + const uint8_t* plaintext, + size_t plaintext_len); + + +/** + * Online symmetric encryption using Ascon128a, finalisation and tag generation. + * + * Finalises the authenticated encryption by returning any remaining buffered + * ciphertext and the authentication tag. The total ciphertext length is + * equal to the total plaintext length. + * + * It will securely erase the content of the \p ctx struct before returning. + * + * The calling order for encryption is: + * 1. ascon_aead128a_init() - once only + * 2. ascon_aead128a_assoc_data_update() - 0 or more times + * 3. ascon_aead128a_encrypt_update() - 0 or more times, see warning + * 4. ascon_aead128a_encrypt_final() - once only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during this function call. In case the encryption session is interrupted + * and never finalised (this function is never called), clear the context with + * ascon_aead_cleanup() to erase the key copy. + * + * @param[in, out] ctx the encryption context, handling the cipher + * state and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] ciphertext trailing encrypted data still available in the + * buffer of the buffered updating. This function will write + * [0, #ASCON_DOUBLE_RATE[ bytes into \p ciphertext. + * The exact number of written bytes is indicated by the return value. + * Not NULL. + * @param[out] tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] tag_len length of the tag to generate in bytes. At least + * #ASCON_AEAD_TAG_MIN_SECURE_LEN is recommended for security. + * @returns number of bytes written into \p ciphertext. The value is in the + * interval [0, #ASCON_DOUBLE_RATE[, i.e. whatever remained in the buffer + * after the last update call. + */ +ASCON_API size_t +ascon_aead128a_encrypt_final(ascon_aead_ctx_t* ctx, + uint8_t* ciphertext, + uint8_t* tag, + size_t tag_len); + +/** + * Offline symmetric decryption using Ascon128a, which uses a double data rate + * compared to Ascon128. + * + * Decrypts the data which is already available as a whole in a contiguous + * buffer, validating any optional associated data in the process. + * Provides the plaintext and the validity of the authentication tag as output. + * The total plaintext length is equal to the total ciphertext length. + * + * In case of no associated data at all to be authenticated, set + * \p assoc_data_len to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * @image html decrypt.png + * + * @param[out] plaintext decrypted data with the same length as the + * ciphertext, thus \p ciphertext_len will be written in this buffer. + * This pointer may also point to the same location as \p ciphertext + * to decrypt the ciphertext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] key secret key of #ASCON_AEAD128a_KEY_LEN bytes. Not NULL. + * @param[in] nonce public unique nonce of #ASCON_AEAD_NONCE_LEN bytes. + * @param[in] assoc_data data to be validated with the same tag + * but not decrypted. Can be NULL iff \p assoc_data_len is 0. + * @param[in] ciphertext data to be decrypted into \p plaintext. + * @param[in] expected_tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. Can be 0. + * @param[in] ciphertext_len length of the data pointed by \p ciphertext in + * bytes. Can be 0 (not recommended, see warning of + * ascon_aead128_encrypt()). + * @param[in] expected_tag_len length of the \p tag to check in bytes. It should be + * the same length as generated during the encryption + * but it can be shorter (although it's not recommended). + * @returns the answer to the question "is tha tag valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the tag is correct, + * thus the associated data and ciphertext are intact and authentic. + * `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_aead128a_decrypt(uint8_t* plaintext, + const uint8_t key[ASCON_AEAD128a_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* ciphertext, + const uint8_t* expected_tag, + size_t assoc_data_len, + size_t ciphertext_len, + size_t expected_tag_len); + +/** + * Online symmetric decryption using Ascon128a, feeding ciphertext and getting + * plaintext. + * + * Feeds a chunk of ciphertext data to the decryption session after any + * optional associated data has been processed. The ciphertext will be decrypted + * and provided back in buffered chunks of #ASCON_RATE bytes. + * + * It will automatically finalise the absorption of any associated data, + * so no new associated data could be processed after this function is called. + * + * The calling order for decryption is: + * 1. ascon_aead128a_init() - once only + * 2. ascon_aead128a_assoc_data_update() - 0 or more times + * 3. ascon_aead128a_decrypt_update() - 0 or more times, see warning + * 4. ascon_aead128a_decrypt_final() - once only + * + * @param[in, out] ctx the decryption context, handling the cipher state + * and buffering of incoming data to be processed. Not NULL. + * @param[out] plaintext decrypted data, buffered into chunks. + * This function will write a multiple of #ASCON_DOUBLE_RATE bytes in the + * interval [0, \p ciphertext_len + #ASCON_DOUBLE_RATE[ into \p plaintext. + * The exact number of written bytes is indicated by the return value. + * This pointer may also point to the same location as \p ciphertext + * to decrypt the ciphertext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] ciphertext data to be decrypted into \p plaintext. All of the + * ciphertext will be processed, even if the function provides less than + * \p ciphertext_len output bytes. They are just buffered. Not NULL. + * @param[in] ciphertext_len length of the data pointed by \p ciphertext in + * bytes. May be 0. + * @returns number of bytes written into \p plaintext. The value is a multiple + * of #ASCON_DOUBLE_RATE in [0, \p ciphertext_len + #ASCON_DOUBLE_RATE[. + */ +ASCON_API size_t +ascon_aead128a_decrypt_update(ascon_aead_ctx_t* ctx, + uint8_t* plaintext, + const uint8_t* ciphertext, + size_t ciphertext_len); + + +/** + * Online symmetric decryption using Ascon128a, finalisation and tag validation. + * + * Finalises the authenticated decryption by returning any remaining buffered + * plaintext and the validity of the authentication tag. + * The total plaintext length is equal to the total ciphertext length. + * + * It will securely erase the content of the \p ctx struct before returning. + * + * The calling order for decryption is: + * 1. ascon_aead128a_init() - once only + * 2. ascon_aead128a_assoc_data_update() - 0 or more times + * 3. ascon_aead128a_decrypt_update() - 0 or more times, see warning + * 4. ascon_aead128a_decrypt_final() - once only + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during this function call. In case the decryption session is interrupted + * and never finalised (this function is never called), clear the context with + * ascon_aead_cleanup() to erase the key copy. + * + * @param[in, out] ctx the decryption context, handling the cipher + * state and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] plaintext trailing decrypted data still available in the + * buffer of the buffered updating. This function will write + * [0, #ASCON_DOUBLE_RATE[ bytes into \p plaintext. + * The exact number of written bytes is indicated by the return value. + * Not NULL. + * @param[out] is_tag_valid the answer to the question "is the tag valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the tag is correct, + * thus the associated data and ciphertext are intact and authentic. + * `false` (== #ASCON_TAG_INVALID) otherwise. + * @param[in] expected_tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] expected_tag_len length of the \p tag to check in bytes. It should be + * the same length as generated during the encryption + * but it can be shorter (although it's not recommended). + * @returns number of bytes written into \p plaintext. The value is in the + * interval [0, #ASCON_DOUBLE_RATE[, i.e. whatever remained in the buffer + * after the last update call. + */ +ASCON_API size_t +ascon_aead128a_decrypt_final(ascon_aead_ctx_t* ctx, + uint8_t* plaintext, + bool* is_tag_valid, + const uint8_t* expected_tag, + size_t expected_tag_len); + + +/** + * Offline symmetric encryption using Ascon80pq, which uses a larger key. + * + * Encrypts the data which is already available as a whole in a contiguous + * buffer, authenticating any optional associated data in the process. + * Provides the ciphertext and the authentication tag as output. + * + * In case of no associated data at all to be authenticated, set + * \p assoc_data_len to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * @image html encrypt.png + * + * @warning + * The nonce **must be unique**, as the strength of the AEAD is based on + * its uniqueness. + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the + * form `Hash(key || msg)`. + * + * @param[out] ciphertext encrypted data with the same length as the + * plaintext, thus \p plaintext_len will be written in this buffer. + * This pointer may also point to the same location as \p plaintext + * to encrypt the plaintext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[out] tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] key secret key of #ASCON_AEAD80pq_KEY_LEN bytes. Not NULL. + * @param[in] nonce public **unique** nonce of #ASCON_AEAD_NONCE_LEN bytes. + * @param[in] assoc_data data to be authenticated with the same tag + * but not encrypted. Can be NULL iff \p assoc_data_len is 0. + * @param[in] plaintext data to be encrypted into \p ciphertext. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. Can be 0. + * @param[in] plaintext_len length of the data pointed by \p plaintext in + * bytes. Can be 0 (not recommended, see warning). + * @param[in] tag_len length of the tag to generate in bytes. At least + * #ASCON_AEAD_TAG_MIN_SECURE_LEN is recommended for security. +*/ +ASCON_API void +ascon_aead80pq_encrypt(uint8_t* ciphertext, + uint8_t* tag, + const uint8_t key[ASCON_AEAD80pq_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* plaintext, + size_t assoc_data_len, + size_t plaintext_len, + size_t tag_len); + + +/** + * Online symmetric encryption/decryption using Ascon80pq, initialisation - + * note that a larger key is used compared to Ascon128 and Ascon128a. + * + * Prepares to start a new encryption or decryption session for plaintext or + * ciphertext and associated data being provided one chunk at the time. + * + * The key and nonce are copied/absorbed into the internal state, so they can + * be deleted from their original location after this function returns. + * + * The calling order for encryption/decryption is: + * 1. ascon_aead80pq_init() - once only + * 2. ascon_aead80pq_assoc_data_update() - 0 or more times + * 3. ascon_aead80pq_encrypt_update() / ascon_aead80pq_decrypt_update() - 0 or + * more times, see warning + * 4. ascon_aead80pq_encrypt_final() / ascon_aead80pq_encrypt_final() - once + * only + * + * @image html encrypt.png + * @image html decrypt.png + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during the ascon_aead80pq_encrypt_final() or ascon_aead80pq_decrypt_final() + * call. In case the encryption or decryption session is interrupted and never + * finalised, clear the context with ascon_aead_cleanup() to erase the key copy. + * + * @warning + * Do not mix Init-Update-Final functions across ciphers. + * + * @param[in, out] ctx the encryption/decryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[in] key secret key of #ASCON_AEAD80pq_KEY_LEN bytes. Not NULL + * @param[in] nonce public unique nonce of #ASCON_AEAD_NONCE_LEN bytes. Not + * NULL. + */ +ASCON_API void +ascon_aead80pq_init(ascon_aead_ctx_t* ctx, + const uint8_t key[ASCON_AEAD80pq_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN]); + +/** + * Online symmetric encryption/decryption using Ascon80pq, feeding associated + * data. + * + * Feeds a chunk of associated data to the already initialised encryption + * or decryption session. The data will be authenticated by the tag provided by + * the final function, but not encrypted or decrypted. + * + * In case of no associated data at all to be authenticated/validated, this + * function can either be skipped completely or called (also many times) + * with \p assoc_data_len set to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * After calling ascon_aead80pq_encrypt_update() or + * ascon_aead80pq_decrypt_update(), this function **must** not be used anymore + * on the same context. + * + * The calling order for encryption/decryption is: + * 1. ascon_aead80pq_init() - once only + * 2. ascon_aead80pq_assoc_data_update() - 0 or more times + * 3. ascon_aead80pq_encrypt_update() / ascon_aead80pq_decrypt_update() - 0 or + * more times, see warning + * 4. ascon_aead80pq_encrypt_final() / ascon_aead80pq_encrypt_final() - once + * only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[in, out] ctx the encryption/decryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[in] assoc_data data to be authenticated/validated with the same tag + * but not encrypted/decrypted. May be NULL iff \p assoc_data_len is 0. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. May be 0. + */ +ASCON_API void +ascon_aead80pq_assoc_data_update(ascon_aead_ctx_t* ctx, + const uint8_t* assoc_data, + size_t assoc_data_len); + +/** + * Online symmetric encryption using Ascon80pq, feeding plaintext and getting + * ciphertext. + * + * Feeds a chunk of plaintext data to the encryption session after any + * optional associated data has been processed. The plaintext will be encrypted + * and provided as ciphertext in buffered chunks of #ASCON_RATE bytes. + * + * It will automatically finalise the absorption of any associated data, + * so no new associated data could be processed after this function is called. + * + * The calling order for encryption is: + * 1. ascon_aead80pq_init() - once only + * 2. ascon_aead80pq_assoc_data_update() - 0 or more times + * 3. ascon_aead80pq_encrypt_update() - 0 or more times, see warning + * 4. ascon_aead80pq_encrypt_final() - once only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @param[in, out] ctx the encryption context, handling the cipher + * state and buffering of incoming data to be processed. Not NULL. + * @param[out] ciphertext encrypted data, buffered into chunks. + * This function will write a multiple of #ASCON_RATE bytes in the + * interval [0, \p plaintext_len + #ASCON_RATE[ into \p ciphertext. + * The exact number of written bytes is indicated by the return value. + * This pointer may also point to the same location as \p plaintext + * to encrypt the plaintext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] plaintext data to be encrypted into \p ciphertext. All of the + * plaintext will be processed, even if the function provides less than + * \p plaintext_len output bytes. They are just buffered. Not NULL. + * @param[in] plaintext_len length of the data pointed by \p plaintext in + * bytes. May be 0. + * @returns number of bytes written into \p ciphertext. The value is a multiple + * of #ASCON_RATE in [0, \p plaintext_len + #ASCON_RATE[. + */ +ASCON_API size_t +ascon_aead80pq_encrypt_update(ascon_aead_ctx_t* ctx, + uint8_t* ciphertext, + const uint8_t* plaintext, + size_t plaintext_len); + + +/** + * Online symmetric encryption using Ascon80pq, finalisation and tag generation. + * + * Finalises the authenticated encryption by returning any remaining buffered + * ciphertext and the authentication tag. The total ciphertext length is + * equal to the total plaintext length. + * + * It will securely erase the content of the \p ctx struct before returning. + * + * The calling order for encryption is: + * 1. ascon_aead80pq_init() - once only + * 2. ascon_aead80pq_assoc_data_update() - 0 or more times + * 3. ascon_aead80pq_encrypt_update() - 0 or more times, see warning + * 4. ascon_aead80pq_encrypt_final() - once only + * + * @warning + * Using the AEAD to just authenticate any associated data with no + * plaintext to be encrypted is not recommended as the AEAD algorithm is not + * designed for that. Instead, use the Ascon hashing or xof functions in the form + * `Hash(key || msg)`. + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during this function call. In case the encryption session is interrupted + * and never finalised (this function is never called), clear the context with + * ascon_aead_cleanup() to erase the key copy. + * + * @param[in, out] ctx the encryption context, handling the cipher + * state and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] ciphertext trailing encrypted data still available in the + * buffer of the buffered updating. This function will write + * [0, #ASCON_RATE[ bytes into \p ciphertext. + * The exact number of written bytes is indicated by the return value. + * Not NULL. + * @param[out] tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] tag_len length of the tag to generate in bytes. At least + * #ASCON_AEAD_TAG_MIN_SECURE_LEN is recommended for security. + * @returns number of bytes written into \p ciphertext. The value is in the + * interval [0, #ASCON_RATE[, i.e. whatever remained in the buffer + * after the last update call. + */ +ASCON_API size_t +ascon_aead80pq_encrypt_final(ascon_aead_ctx_t* ctx, + uint8_t* ciphertext, + uint8_t* tag, + size_t tag_len); + +/** + * Offline symmetric decryption using Ascon80pq, which uses a larger key + * compared to Ascon128. + * + * Decrypts the data which is already available as a whole in a contiguous + * buffer, validating any optional associated data in the process. + * Provides the plaintext and the validity of the authentication tag as output. + * The total plaintext length is equal to the total ciphertext length. + * + * In case of no associated data at all to be authenticated, set + * \p assoc_data_len to 0. Iff that is the case, \p assoc_data can + * be set to NULL. + * + * @image html decrypt.png + * + * @param[out] plaintext decrypted data with the same length as the + * ciphertext, thus \p ciphertext_len will be written in this buffer. + * This pointer may also point to the same location as \p ciphertext + * to decrypt the ciphertext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] key secret key of #ASCON_AEAD80pq_KEY_LEN bytes. Not NULL. + * @param[in] nonce public unique nonce of #ASCON_AEAD_NONCE_LEN bytes. + * @param[in] assoc_data data to be validated with the same tag + * but not decrypted. Can be NULL iff \p assoc_data_len is 0. + * @param[in] ciphertext data to be decrypted into \p plaintext. + * @param[in] expected_tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] assoc_data_len length of the data pointed by \p assoc_data in + * bytes. Can be 0. + * @param[in] ciphertext_len length of the data pointed by \p ciphertext in + * bytes. Can be 0 (not recommended, see warning of + * ascon_aead128_encrypt()). + * @param[in] expected_tag_len length of the \p tag to check in bytes. It should be + * the same length as generated during the encryption + * but it can be shorter (although it's not recommended). + * @returns the answer to the question "is tha tag valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the tag is correct, + * thus the associated data and ciphertext are intact and authentic. + * `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_aead80pq_decrypt(uint8_t* plaintext, + const uint8_t key[ASCON_AEAD80pq_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* ciphertext, + const uint8_t* expected_tag, + size_t assoc_data_len, + size_t ciphertext_len, + size_t expected_tag_len); + +/** + * Online symmetric decryption using Ascon80pq, feeding ciphertext and getting + * plaintext. + * + * Feeds a chunk of ciphertext data to the decryption session after any + * optional associated data has been processed. The ciphertext will be decrypted + * and provided back in buffered chunks of #ASCON_RATE bytes. + * + * It will automatically finalise the absorption of any associated data, + * so no new associated data could be processed after this function is called. + * + * The calling order for decryption is: + * 1. ascon_aead80pq_init() - once only + * 2. ascon_aead80pq_assoc_data_update() - 0 or more times + * 3. ascon_aead80pq_decrypt_update() - 0 or more times, see warning + * 4. ascon_aead80pq_decrypt_final() - once only + * + * @param[in, out] ctx the decryption context, handling the cipher state + * and buffering of incoming data to be processed. Not NULL. + * @param[out] plaintext decrypted data, buffered into chunks. + * This function will write a multiple of #ASCON_RATE bytes in the + * interval [0, \p ciphertext_len + #ASCON_RATE[ into \p plaintext. + * The exact number of written bytes is indicated by the return value. + * This pointer may also point to the same location as \p ciphertext + * to decrypt the ciphertext in-place, sparing on memory instead + * of writing into a separate output buffer. Not NULL. + * @param[in] ciphertext data to be decrypted into \p plaintext. All of the + * ciphertext will be processed, even if the function provides less than + * \p ciphertext_len output bytes. They are just buffered. Not NULL. + * @param[in] ciphertext_len length of the data pointed by \p ciphertext in + * bytes. May be 0. + * @returns number of bytes written into \p plaintext. The value is a multiple + * of #ASCON_RATE in [0, \p ciphertext_len + #ASCON_RATE[. + */ +ASCON_API size_t +ascon_aead80pq_decrypt_update(ascon_aead_ctx_t* ctx, + uint8_t* plaintext, + const uint8_t* ciphertext, + size_t ciphertext_len); + +/** + * Online symmetric decryption using Ascon80pq, finalisation and tag validation. + * + * Finalises the authenticated decryption by returning any remaining buffered + * plaintext and the validity of the authentication tag. + * The total plaintext length is equal to the total ciphertext length. + * + * It will securely erase the content of the \p ctx struct before returning. + * + * The calling order for decryption is: + * 1. ascon_aead80pq_init() - once only + * 2. ascon_aead80pq_assoc_data_update() - 0 or more times + * 3. ascon_aead80pq_decrypt_update() - 0 or more times, see warning + * 4. ascon_aead80pq_decrypt_final() - once only + * + * @warning + * A copy of the secret key is kept in the \p ctx struct and securely erased + * during this function call. In case the decryption session is interrupted + * and never finalised (this function is never called), clear the context with + * ascon_aead_cleanup() to erase the key copy. + * + * @param[in, out] ctx the decryption context, handling the cipher + * state and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] plaintext trailing decrypted data still available in the + * buffer of the buffered updating. This function will write + * [0, #ASCON_RATE[ bytes into \p plaintext. + * The exact number of written bytes is indicated by the return value. + * Not NULL. + * @param[out] is_tag_valid the answer to the question "is the tag valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the tag is correct, + * thus the associated data and ciphertext are intact and authentic. + * `false` (== #ASCON_TAG_INVALID) otherwise. + * @param[in] expected_tag Message Authentication Code (MAC, a.k.a. cryptographic tag, + * fingerprint), used to validate the integrity and authenticity of the + * associated data and ciphertext. Has \p tag_len bytes. Not NULL. + * @param[in] expected_tag_len length of the \p tag to check in bytes. It should be + * the same length as generated during the encryption + * but it can be shorter (although it's not recommended). + * @returns number of bytes written into \p plaintext. The value is in the + * interval [0, #ASCON_RATE[, i.e. whatever remained in the buffer + * after the last update call. + */ +ASCON_API size_t +ascon_aead80pq_decrypt_final(ascon_aead_ctx_t* ctx, + uint8_t* plaintext, + bool* is_tag_valid, + const uint8_t* expected_tag, + size_t expected_tag_len); + +/** + * Offline Ascon-Hash with fixed digest length. + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash(key || msg)`. There + * is **no need to build an HMAC** construct around it, as it does not suffer + * from length-extension vulnerabilities. + * + * @param[out] digest fingerprint of the message, output of the hash function, + * of #ASCON_HASH_DIGEST_LEN bytes. + * @param[in] data message fed into the hash function. + * @param[in] data_len length of \p data in bytes. + */ +ASCON_API void +ascon_hash(uint8_t digest[ASCON_HASH_DIGEST_LEN], + const uint8_t* data, + size_t data_len); + +/** + * Offline Ascon-Hasha with fixed digest length. + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash(key || msg)`. There + * is **no need to build an HMAC** construct around it, as it does not suffer + * from length-extension vulnerabilities. + * + * @param[out] digest fingerprint of the message, output of the hash function, + * of #ASCON_HASH_DIGEST_LEN bytes. + * @param[in] data message fed into the hash function. + * @param[in] data_len length of \p data in bytes. + */ +ASCON_API void +ascon_hasha(uint8_t digest[ASCON_HASHA_DIGEST_LEN], + const uint8_t* data, + size_t data_len); + +/** + * Offline Ascon-Hash with fixed digest length, finalisation and digest + * validation of the expected one. + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash(key || msg)`. There + * is **no need to build an HMAC** construct around it, as it does not suffer + * from length-extension vulnerabilities. + * + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_final() or ascon_hash() function, + * of #ASCON_HASH_DIGEST_LEN bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @param[in] data message fed into the hash function. + * @param[in] data_len length of \p data in bytes. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hash_matches(const uint8_t expected_digest[ASCON_HASH_DIGEST_LEN], + const uint8_t* data, + size_t data_len); + +/** + * Offline Ascon-Hasha with fixed digest length, finalisation and digest + * validation of the expected one. + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash(key || msg)`. There + * is **no need to build an HMAC** construct around it, as it does not suffer + * from length-extension vulnerabilities. + * + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_final() or ascon_hash() function, + * of #ASCON_HASH_DIGEST_LEN bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @param[in] data message fed into the hash function. + * @param[in] data_len length of \p data in bytes. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hasha_matches(const uint8_t expected_digest[ASCON_HASHA_DIGEST_LEN], + const uint8_t* data, + size_t data_len); + +/** + * Online Ascon-Hash with fixed digest length, initialisation. + * + * Prepares to start a new hashing session to get a digest of + * #ASCON_HASH_DIGEST_LEN bytes. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash(key || msg)`. There + * is **no need to build an HMAC** construct around it, as it does not suffer + * from length-extension vulnerabilities. + * + * @warning + * Do not mix Init-Update-Final functions of Ascon-Hash and Ascon-XOF. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + */ +ASCON_API void +ascon_hash_init(ascon_hash_ctx_t* ctx); + +/** + * Online Ascon-Hasha with fixed digest length, initialisation. + * + * Prepares to start a new hashing session to get a digest of + * #ASCON_HASH_DIGEST_LEN bytes. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash(key || msg)`. There + * is **no need to build an HMAC** construct around it, as it does not suffer + * from length-extension vulnerabilities. + * + * @warning + * Do not mix Init-Update-Final functions of Ascon-Hash and Ascon-XOF. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + */ +ASCON_API void +ascon_hasha_init(ascon_hash_ctx_t* ctx); + +/** + * Online Ascon-Hash with fixed digest length, feeding data to hash. + * + * Feeds a chunk of data to the already initialised hashing session. + * + * In case of no data at all to be hashed, this function can be called (also + * many times) with \p data_len set to 0. Iff that is the case, \p data can be + * set to NULL. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + * @param[in] data bytes to be hashes. May be NULL iff \p data_len is 0. + * @param[in] data_len length of the \p data pointed by in bytes. May be 0. + */ +ASCON_API void +ascon_hash_update(ascon_hash_ctx_t* ctx, + const uint8_t* data, + size_t data_len); + +/** + * Online Ascon-Hasha with fixed digest length, feeding data to hash. + * + * Feeds a chunk of data to the already initialised hashing session. + * + * In case of no data at all to be hashed, this function can be called (also + * many times) with \p data_len set to 0. Iff that is the case, \p data can be + * set to NULL. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + * @param[in] data bytes to be hashes. May be NULL iff \p data_len is 0. + * @param[in] data_len length of the \p data pointed by in bytes. May be 0. + */ +ASCON_API void +ascon_hasha_update(ascon_hash_ctx_t* ctx, + const uint8_t* data, + size_t data_len); + +/** + * Online Ascon-Hash with fixed digest length, finalisation and digest + * generation. + * + * Finalises the hashing by returning the digest of the message. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @warning + * In case the hashing session is interrupted and never finalised (this function + * is never called), clear the context with ascon_hash_cleanup() to erase any + * information about the hashed content, especially in case keyed hashing is + * performed. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] digest fingerprint of the message, output of the hash function, + * of #ASCON_HASH_DIGEST_LEN bytes. + */ +ASCON_API void +ascon_hash_final(ascon_hash_ctx_t* ctx, + uint8_t digest[ASCON_HASH_DIGEST_LEN]); + +/** + * Online Ascon-Hasha with fixed digest length, finalisation and digest + * generation. + * + * Finalises the hashing by returning the digest of the message. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @warning + * In case the hashing session is interrupted and never finalised (this function + * is never called), clear the context with ascon_hash_cleanup() to erase any + * information about the hashed content, especially in case keyed hashing is + * performed. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] digest fingerprint of the message, output of the hash function, + * of #ASCON_HASH_DIGEST_LEN bytes. + */ +ASCON_API void +ascon_hasha_final(ascon_hash_ctx_t* ctx, + uint8_t digest[ASCON_HASHA_DIGEST_LEN]); + +/** + * Online Ascon-Hasha with fixed digest length, finalisation and digest + * validation of the expected one. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @warning + * In case the hashing session is interrupted and never finalised (this function + * is never called), clear the context with ascon_hash_cleanup() to erase any + * information about the hashed content, especially in case keyed hashing is + * performed. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_final() or ascon_hash() function, + * of #ASCON_HASH_DIGEST_LEN bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hash_final_matches(ascon_hash_ctx_t* ctx, + const uint8_t expected_digest[ASCON_HASH_DIGEST_LEN]); + +/** + * Online Ascon-Hasha with fixed digest length, finalisation and digest + * validation of the expected one. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @warning + * In case the hashing session is interrupted and never finalised (this function + * is never called), clear the context with ascon_hash_cleanup() to erase any + * information about the hashed content, especially in case keyed hashing is + * performed. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_final() or ascon_hash() function, + * of #ASCON_HASH_DIGEST_LEN bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hasha_final_matches(ascon_hash_ctx_t* ctx, + const uint8_t expected_digest[ASCON_HASHA_DIGEST_LEN]); + +/** + * Offline Ascon-Hash with custom digest length (eXtendable Output Function, + * XOF). + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it of the desired length. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash_xof(key || msg)`. + * There is **no need to build an HMAC** construct around it, as it does not + * suffer from length-extension vulnerabilities. + * + * @warning + * To have 128 bits of security against birthday attacks (collisions), + * a digest length of at least 256 bits (32 bytes) is recommended. Against + * quantum computers, the hash size should be double the amount of wanted + * security bits. + * + * @param[out] digest fingerprint of the message, output of the hash function, + * of \p digest_len bytes. + * @param[in] data message fed into the hash function. + * @param[in] digest_len desired length of the \p digest in bytes. + * @param[in] data_len length of \p data in bytes. + */ +ASCON_API void +ascon_hash_xof(uint8_t* digest, + const uint8_t* data, + size_t digest_len, + size_t data_len); + +/** + * Offline Ascon-Hasha with custom digest length (eXtendable Output Function, + * XOF). + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it of the desired length. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash_xof(key || msg)`. + * There is **no need to build an HMAC** construct around it, as it does not + * suffer from length-extension vulnerabilities. + * + * @warning + * To have 128 bits of security against birthday attacks (collisions), + * a digest length of at least 256 bits (32 bytes) is recommended. Against + * quantum computers, the hash size should be double the amount of wanted + * security bits. + * + * @param[out] digest fingerprint of the message, output of the hash function, + * of \p digest_len bytes. + * @param[in] data message fed into the hash function. + * @param[in] digest_len desired length of the \p digest in bytes. + * @param[in] data_len length of \p data in bytes. + */ +ASCON_API void +ascon_hasha_xof(uint8_t* digest, + const uint8_t* data, + size_t digest_len, + size_t data_len); + +/** + * Offline Ascon-Hash with custom digest length (eXtendable Output Function, + * XOF) and validation of the expected one. + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it of the desired length. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash_xof(key || msg)`. + * There is **no need to build an HMAC** construct around it, as it does not + * suffer from length-extension vulnerabilities. + * + * @warning + * To have 128 bits of security against birthday attacks (collisions), + * a digest length of at least 256 bits (32 bytes) is recommended. Against + * quantum computers, the hash size should be double the amount of wanted + * security bits. + * + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_xof_final() or ascon_hash_xof() function, + * of \p expected_digest_len bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @param[in] data message fed into the hash function. + * @param[in] expected_digest_len desired length of the \p expected_digest + * in bytes. + * @param[in] data_len length of \p data in bytes. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hash_xof_matches(const uint8_t* expected_digest, + const uint8_t* data, + size_t expected_digest_len, + size_t data_len); + +/** + * Offline Ascon-Hasha with custom digest length (eXtendable Output Function, + * XOF) and validation of the expected one. + * + * Hashes the data, which is already available as a whole in a contiguous + * buffer, and provides the digest for it of the desired length. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash_xof(key || msg)`. + * There is **no need to build an HMAC** construct around it, as it does not + * suffer from length-extension vulnerabilities. + * + * @warning + * To have 128 bits of security against birthday attacks (collisions), + * a digest length of at least 256 bits (32 bytes) is recommended. Against + * quantum computers, the hash size should be double the amount of wanted + * security bits. + * + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_xof_final() or ascon_hash_xof() function, + * of \p expected_digest_len bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @param[in] data message fed into the hash function. + * @param[in] expected_digest_len desired length of the \p expected_digest + * in bytes. + * @param[in] data_len length of \p data in bytes. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hasha_xof_matches(const uint8_t* expected_digest, + const uint8_t* data, + size_t expected_digest_len, + size_t data_len); + +/** + * Online Ascon-Hash with custom digest length (eXtendable Output Function, + * XOF), initialisation. + * + * Prepares to start a new hashing session to get a digest of custom length. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash_xof(key || msg)`. + * There is **no need to build an HMAC** construct around it, as it does not + * suffer from length-extension vulnerabilities. + * + * @warning + * Do not mix Init-Update-Final functions of Ascon-Hash and Ascon-XOF. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + */ +ASCON_API void +ascon_hash_xof_init(ascon_hash_ctx_t* ctx); + +/** + * Online Ascon-Hasha with custom digest length (eXtendable Output Function, + * XOF), initialisation. + * + * Prepares to start a new hashing session to get a digest of custom length. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @image html hash.png + * + * @remark + * This function can be used for keyed hashing to generate a MAC by simply + * prepending a secret key to the message, like `ascon_hash_xof(key || msg)`. + * There is **no need to build an HMAC** construct around it, as it does not + * suffer from length-extension vulnerabilities. + * + * @warning + * Do not mix Init-Update-Final functions of Ascon-Hash and Ascon-XOF. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + */ +ASCON_API void +ascon_hasha_xof_init(ascon_hash_ctx_t* ctx); + +/** + * Online Ascon-Hash with custom digest length (eXtendable Output Function, + * XOF), feeding data to hash. + * + * Feeds a chunk of data to the already initialised hashing session. + * + * In case of no data at all to be hashed, this function can be called (also + * many times) with \p data_len set to 0. Iff that is the case, \p data can be + * set to NULL. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + * @param[in] data bytes to be hashes. May be NULL iff \p data_len is 0. + * @param[in] data_len length of the \p data pointed by in bytes. May be 0. + */ +ASCON_API void +ascon_hash_xof_update(ascon_hash_ctx_t* ctx, + const uint8_t* data, + size_t data_len); + +/** + * Online Ascon-Hasha with custom digest length (eXtendable Output Function, + * XOF), feeding data to hash. + * + * Feeds a chunk of data to the already initialised hashing session. + * + * In case of no data at all to be hashed, this function can be called (also + * many times) with \p data_len set to 0. Iff that is the case, \p data can be + * set to NULL. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. Not NULL. + * @param[in] data bytes to be hashes. May be NULL iff \p data_len is 0. + * @param[in] data_len length of the \p data pointed by in bytes. May be 0. + */ +ASCON_API void +ascon_hasha_xof_update(ascon_hash_ctx_t* ctx, + const uint8_t* data, + size_t data_len); + +/** + * Online Ascon-Hash with custom digest length (eXtendable Output Function, + * XOF), finalisation and digest generation. + * + * Finalises the hashing by returning the digest of the message. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @warning + * To have 128 bits of security against birthday attacks (collisions), + * a digest length of at least 256 bits (32 bytes) is recommended. Against + * quantum computers, the hash size should be double the amount of wanted + * security bits. + * + * @warning + * In case the hashing session is interrupted and never finalised (this function + * is never called), clear the context with ascon_hash_cleanup() to erase any + * information about the hashed content, especially in case keyed hashing is + * performed. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] digest fingerprint of the message, output of the hash function, + * of \p digest_size bytes. + * @param[in] digest_len desired length of the \p digest in bytes. + */ +ASCON_API void +ascon_hash_xof_final(ascon_hash_ctx_t* ctx, + uint8_t* digest, + size_t digest_len); + +/** + * Online Ascon-Hasha with custom digest length (eXtendable Output Function, + * XOF), finalisation and digest generation. + * + * Finalises the hashing by returning the digest of the message. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @warning + * To have 128 bits of security against birthday attacks (collisions), + * a digest length of at least 256 bits (32 bytes) is recommended. Against + * quantum computers, the hash size should be double the amount of wanted + * security bits. + * + * @warning + * In case the hashing session is interrupted and never finalised (this function + * is never called), clear the context with ascon_hash_cleanup() to erase any + * information about the hashed content, especially in case keyed hashing is + * performed. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[out] digest fingerprint of the message, output of the hash function, + * of \p digest_size bytes. + * @param[in] digest_len desired length of the \p digest in bytes. + */ +ASCON_API void +ascon_hasha_xof_final(ascon_hash_ctx_t* ctx, + uint8_t* digest, + size_t digest_len); + +/** + * Online Ascon-Hash with custom digest length (eXtendable Output Function, + * XOF), finalisation and digest validation of the expected one. + * + * Uses 12-round `Pb` permutations during absorption and squeezing. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_xof_final() or ascon_hash_xof() function, + * of \p expected_digest_len bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @param[in] expected_digest_len desired length of the \p expected_digest in + * bytes. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hash_xof_final_matches(ascon_hash_ctx_t* ctx, + const uint8_t* expected_digest, + size_t expected_digest_len); + +/** + * Online Ascon-Hasha with custom digest length (eXtendable Output Function, + * XOF), finalisation and digest validation of the expected one. + * + * Uses 8-round `Pb` permutations during absorption and squeezing, lighter + * version of the Ascon-Hash function. + * + * @param[in, out] ctx the hashing context, handling the hash function state + * and buffering of incoming data to be processed. It will be erased + * securely before this function returns. Not NULL. + * @param[in] expected_digest expected fingerprint of the message, output of + * the ascon_hash_xof_final() or ascon_hash_xof() function, + * of \p expected_digest_len bytes. + * This is the digest that comes with the message and will be compared + * with the internally-generated one by this function. + * @param[in] expected_digest_len desired length of the \p expected_digest in + * bytes. + * @return the answer to the question "is the digest valid?", thus + * `true` (== #ASCON_TAG_OK) if the validation of the digest is correct, + * thus the message is intact (and authentic if a keyed hash was + * performed), `false` (== #ASCON_TAG_INVALID) otherwise. + */ +ASCON_API bool +ascon_hasha_xof_final_matches(ascon_hash_ctx_t* ctx, + const uint8_t* expected_digest, + size_t expected_digest_len); + +/** + * Security cleanup of the hashing context, in case the online + * processing is not completed to the end. + * + * Use this function only when something goes wrong between the calls of + * online hashing or decryption, and you never call the ascon_hash_final() + * or ascon_hash_xof_final() or ascon_hasha_final() + * or ascon_hasha_xof_final() functions (because these functions perform the + * cleanup automatically). + * + * This is to prevent any information to leak through the context in case an + * hashing transaction is rolled back/abruptly terminated, especially parts of + * a key (for keyed hashing) still buffered in the context. + * + * @param[in, out] ctx to erase. + */ +ASCON_API void +ascon_hash_cleanup(ascon_hash_ctx_t* ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* ASCON_H */ diff --git a/lib/LibAscon/library.json b/lib/LibAscon/library.json new file mode 100644 index 0000000000..85ae05e757 --- /dev/null +++ b/lib/LibAscon/library.json @@ -0,0 +1,23 @@ +{ + "name": "LibAscon", + "version": "1.3.0", + "description": "Lightweight Ascon AEAD cipher (minimal subset for Ascon-128)", + "keywords": "ascon, aead, encryption, cryptography, lightweight", + "repository": { + "type": "git", + "url": "https://github.com/TheMatjaz/LibAscon" + }, + "authors": [ + { + "name": "Matjaz", + "url": "https://github.com/TheMatjaz" + } + ], + "license": "CC0-1.0", + "frameworks": "*", + "platforms": "*", + "build": { + "srcDir": "src", + "includeDir": "include" + } +} diff --git a/lib/LibAscon/src/ascon_aead128.c b/lib/LibAscon/src/ascon_aead128.c new file mode 100644 index 0000000000..08e82d3b49 --- /dev/null +++ b/lib/LibAscon/src/ascon_aead128.c @@ -0,0 +1,277 @@ +/** + * @file + * 64-bit optimised implementation of Ascon128 AEAD cipher. + * + * @license Creative Commons Zero (CC0) 1.0 + * @authors see AUTHORS.md file + */ + +#include "ascon.h" +#include "ascon_internal.h" + +ASCON_API void +ascon_aead128_encrypt(uint8_t* ciphertext, + uint8_t* tag, + const uint8_t key[ASCON_AEAD128_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* plaintext, + size_t assoc_data_len, + size_t plaintext_len, + size_t tag_len) +{ + ASCON_ASSERT(plaintext_len == 0 || ciphertext != NULL); + ASCON_ASSERT(tag_len != 0 || tag != NULL); + ASCON_ASSERT(key != NULL); + ASCON_ASSERT(nonce != NULL); + ASCON_ASSERT(assoc_data_len == 0 || assoc_data != NULL); + ASCON_ASSERT(plaintext_len == 0 || plaintext != NULL); + ascon_aead_ctx_t ctx; + ascon_aead128_init(&ctx, key, nonce); + ascon_aead128_assoc_data_update(&ctx, assoc_data, assoc_data_len); + const size_t new_ct_bytes = ascon_aead128_encrypt_update(&ctx, ciphertext, + plaintext, + plaintext_len); + ascon_aead128_encrypt_final(&ctx, ciphertext + new_ct_bytes, + tag, tag_len); +} + +ASCON_API bool +ascon_aead128_decrypt(uint8_t* plaintext, + const uint8_t key[ASCON_AEAD128_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN], + const uint8_t* assoc_data, + const uint8_t* ciphertext, + const uint8_t* expected_tag, + size_t assoc_data_len, + size_t ciphertext_len, + size_t expected_tag_len) +{ + ASCON_ASSERT(ciphertext_len == 0 || plaintext != NULL); + ASCON_ASSERT(key != NULL); + ASCON_ASSERT(nonce != NULL); + ASCON_ASSERT(assoc_data_len == 0 || assoc_data != NULL); + ASCON_ASSERT(ciphertext_len == 0 || ciphertext != NULL); + ASCON_ASSERT(expected_tag_len != 0 || expected_tag != NULL); + ascon_aead_ctx_t ctx; + bool is_tag_valid; + ascon_aead128_init(&ctx, key, nonce); + ascon_aead128_assoc_data_update(&ctx, assoc_data, assoc_data_len); + const size_t new_pt_bytes = ascon_aead128_decrypt_update(&ctx, + plaintext, + ciphertext, + ciphertext_len); + ascon_aead128_decrypt_final(&ctx, plaintext + new_pt_bytes, + &is_tag_valid, expected_tag, expected_tag_len); + return is_tag_valid; +} + +ASCON_API void +ascon_aead128_init(ascon_aead_ctx_t* const ctx, + const uint8_t key[ASCON_AEAD128_KEY_LEN], + const uint8_t nonce[ASCON_AEAD_NONCE_LEN]) +{ + ASCON_ASSERT(ctx != NULL); + ASCON_ASSERT(key != NULL); + ASCON_ASSERT(nonce != NULL); + ascon_aead_init(ctx, key, nonce, ASCON_IV_AEAD128); + ctx->bufstate.flow_state = ASCON_FLOW_AEAD128_80pq_INITIALISED; +} + +/** + * @internal + * Function passed to buffered_accumulation() to absorb the associated data to + * be authenticated, both during encryption and decryption. + */ +static void +absorb_assoc_data(ascon_sponge_t* sponge, + uint8_t* const data_out, + const uint8_t* const data) +{ + (void) data_out; + sponge->x0 ^= bigendian_decode_u64(data); + ascon_permutation_6(sponge); +} + +/** + * @internal + * Function passed to buffered_accumulation() to absorb the ciphertext + * and squeeze out plaintext during decryption. + */ +static void +absorb_ciphertext(ascon_sponge_t* const sponge, + uint8_t* const plaintext, + const uint8_t* const ciphertext) +{ + // Absorb the ciphertext. + const uint64_t c_0 = bigendian_decode_u64(ciphertext); + // Squeeze out some plaintext + bigendian_encode_u64(plaintext, sponge->x0 ^ c_0); + sponge->x0 = c_0; + // Permute the state + ascon_permutation_6(sponge); +} + +/** + * @internal + * Function passed to buffered_accumulation() to absorb the plaintext + * and squeeze out ciphertext during encryption. + */ +static void +absorb_plaintext(ascon_sponge_t* const sponge, + uint8_t* const ciphertext, + const uint8_t* const plaintext) +{ + // Absorb the plaintext. + sponge->x0 ^= bigendian_decode_u64(plaintext); + // Squeeze out some ciphertext + bigendian_encode_u64(ciphertext, sponge->x0); + // Permute the state + ascon_permutation_6(sponge); +} + +ASCON_API void +ascon_aead128_assoc_data_update(ascon_aead_ctx_t* const ctx, + const uint8_t* assoc_data, + size_t assoc_data_len) +{ + ASCON_ASSERT(ctx != NULL); + ASCON_ASSERT(assoc_data_len == 0 || assoc_data != NULL); + ASCON_ASSERT(ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_INITIALISED + || ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_ASSOC_DATA_UPDATED); + if (assoc_data_len > 0) + { + ctx->bufstate.flow_state = ASCON_FLOW_AEAD128_80pq_ASSOC_DATA_UPDATED; + buffered_accumulation(&ctx->bufstate, NULL, assoc_data, + absorb_assoc_data, assoc_data_len, ASCON_RATE); + } +} + +ASCON_API size_t +ascon_aead128_encrypt_update(ascon_aead_ctx_t* const ctx, + uint8_t* ciphertext, + const uint8_t* plaintext, + size_t plaintext_len) +{ + ASCON_ASSERT(ctx != NULL); + ASCON_ASSERT(plaintext_len == 0 || plaintext != NULL); + ASCON_ASSERT(plaintext_len == 0 || ciphertext != NULL); + ASCON_ASSERT(ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_INITIALISED + || ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_ASSOC_DATA_UPDATED + || ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_ENCRYPT_UPDATED); + if (ctx->bufstate.flow_state != ASCON_FLOW_AEAD128_80pq_ENCRYPT_UPDATED) + { + // Finalise the associated data if not already done sos. + ascon_aead128_80pq_finalise_assoc_data(ctx); + } + ctx->bufstate.flow_state = ASCON_FLOW_AEAD128_80pq_ENCRYPT_UPDATED; + // Start absorbing plaintext and simultaneously squeezing out ciphertext + return buffered_accumulation(&ctx->bufstate, ciphertext, plaintext, + absorb_plaintext, plaintext_len, ASCON_RATE); +} + +ASCON_API size_t +ascon_aead128_encrypt_final(ascon_aead_ctx_t* const ctx, + uint8_t* const ciphertext, + uint8_t* tag, + size_t tag_len) +{ + ASCON_ASSERT(ctx != NULL); + ASCON_ASSERT(ciphertext != NULL); + ASCON_ASSERT(tag_len == 0 || tag != NULL); + ASCON_ASSERT(ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_INITIALISED + || ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_ASSOC_DATA_UPDATED + || ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_ENCRYPT_UPDATED); + if (ctx->bufstate.flow_state != ASCON_FLOW_AEAD128_80pq_ENCRYPT_UPDATED) + { + // Finalise the associated data if not already done sos. + ascon_aead128_80pq_finalise_assoc_data(ctx); + } + size_t freshly_generated_ciphertext_len = 0; + // If there is any remaining less-than-a-block plaintext to be absorbed + // cached in the buffer, pad it and absorb it. + ctx->bufstate.sponge.x0 ^= bigendian_decode_varlen(ctx->bufstate.buffer, + ctx->bufstate.buffer_len); + ctx->bufstate.sponge.x0 ^= PADDING(ctx->bufstate.buffer_len); + // Squeeze out last ciphertext bytes, if any. + bigendian_encode_varlen(ciphertext, ctx->bufstate.sponge.x0, ctx->bufstate.buffer_len); + freshly_generated_ciphertext_len += ctx->bufstate.buffer_len; + // End of encryption, start of tag generation. + // Apply key twice more with a permutation to set the state for the tag. + ctx->bufstate.sponge.x1 ^= ctx->k0; + ctx->bufstate.sponge.x2 ^= ctx->k1; + ascon_permutation_12(&ctx->bufstate.sponge); + ctx->bufstate.sponge.x3 ^= ctx->k0; + ctx->bufstate.sponge.x4 ^= ctx->k1; + // Squeeze out tag into its buffer. + ascon_aead_generate_tag(ctx, tag, tag_len); + // Final security cleanup of the internal state, key and buffer. + ascon_aead_cleanup(ctx); + return freshly_generated_ciphertext_len; +} + +ASCON_API size_t +ascon_aead128_decrypt_update(ascon_aead_ctx_t* const ctx, + uint8_t* plaintext, + const uint8_t* ciphertext, + size_t ciphertext_len) +{ + ASCON_ASSERT(ctx != NULL); + ASCON_ASSERT(ciphertext_len == 0 || ciphertext != NULL); + ASCON_ASSERT(ciphertext_len == 0 || plaintext != NULL); + ASCON_ASSERT(ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_INITIALISED + || ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_ASSOC_DATA_UPDATED + || ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_DECRYPT_UPDATED); + if (ctx->bufstate.flow_state != ASCON_FLOW_AEAD128_80pq_DECRYPT_UPDATED) + { + // Finalise the associated data if not already done sos. + ascon_aead128_80pq_finalise_assoc_data(ctx); + } + ctx->bufstate.flow_state = ASCON_FLOW_AEAD128_80pq_DECRYPT_UPDATED; + // Start absorbing ciphertext and simultaneously squeezing out plaintext + return buffered_accumulation(&ctx->bufstate, plaintext, ciphertext, + absorb_ciphertext, ciphertext_len, ASCON_RATE); +} + +ASCON_API size_t +ascon_aead128_decrypt_final(ascon_aead_ctx_t* const ctx, + uint8_t* plaintext, + bool* const is_tag_valid, + const uint8_t* const expected_tag, + size_t expected_tag_len) +{ + ASCON_ASSERT(ctx != NULL); + ASCON_ASSERT(plaintext != NULL); + ASCON_ASSERT(expected_tag_len == 0 || expected_tag != NULL); + ASCON_ASSERT(is_tag_valid != NULL); + if (ctx->bufstate.flow_state != ASCON_FLOW_AEAD128_80pq_DECRYPT_UPDATED) + { + // Finalise the associated data if not already done sos. + ascon_aead128_80pq_finalise_assoc_data(ctx); + } + size_t freshly_generated_plaintext_len = 0; + // If there is any remaining less-than-a-block ciphertext to be absorbed + // cached in the buffer, pad it and absorb it. + const uint64_t c_0 = bigendian_decode_varlen(ctx->bufstate.buffer, + ctx->bufstate.buffer_len); + // Squeeze out last plaintext bytes, if any. + bigendian_encode_varlen(plaintext, ctx->bufstate.sponge.x0 ^ c_0, + ctx->bufstate.buffer_len); + freshly_generated_plaintext_len += ctx->bufstate.buffer_len; + // Final state changes at decryption's end + ctx->bufstate.sponge.x0 &= ~mask_most_signif_bytes(ctx->bufstate.buffer_len); + ctx->bufstate.sponge.x0 |= c_0; + ctx->bufstate.sponge.x0 ^= PADDING(ctx->bufstate.buffer_len); + // End of decryption, start of tag validation. + // Apply key twice more with a permutation to set the state for the tag. + ctx->bufstate.sponge.x1 ^= ctx->k0; + ctx->bufstate.sponge.x2 ^= ctx->k1; + ascon_permutation_12(&ctx->bufstate.sponge); + ctx->bufstate.sponge.x3 ^= ctx->k0; + ctx->bufstate.sponge.x4 ^= ctx->k1; + // Validate tag with variable len + *is_tag_valid = ascon_aead_is_tag_valid(ctx, expected_tag, expected_tag_len); + // Final security cleanup of the internal state and key. + ascon_aead_cleanup(ctx); + return freshly_generated_plaintext_len; +} diff --git a/lib/LibAscon/src/ascon_aead_common.c b/lib/LibAscon/src/ascon_aead_common.c new file mode 100644 index 0000000000..e79d75edf1 --- /dev/null +++ b/lib/LibAscon/src/ascon_aead_common.c @@ -0,0 +1,148 @@ +/** + * @file + * Code used in multiple AEAD versions of Ascon. + * + * @license Creative Commons Zero (CC0) 1.0 + * @authors see AUTHORS.md file + */ + +#include "ascon.h" +#include "ascon_internal.h" + +void +ascon_aead_init(ascon_aead_ctx_t* const ctx, + const uint8_t* const key, + const uint8_t* const nonce, + const uint64_t iv) +{ + // Store the key in the context as it's required in the final step. + ctx->k0 = bigendian_decode_u64(key); + ctx->k1 = bigendian_decode_u64(key + sizeof(uint64_t)); + ctx->bufstate.sponge.x0 = iv; + ctx->bufstate.sponge.x1 = ctx->k0; + ctx->bufstate.sponge.x2 = ctx->k1; + ctx->bufstate.sponge.x3 = bigendian_decode_u64(nonce); + ctx->bufstate.sponge.x4 = bigendian_decode_u64(nonce + sizeof(uint64_t)); + ascon_permutation_12(&ctx->bufstate.sponge); + ctx->bufstate.sponge.x3 ^= ctx->k0; + ctx->bufstate.sponge.x4 ^= ctx->k1; + ctx->bufstate.buffer_len = 0; +} + +void +ascon_aead128_80pq_finalise_assoc_data(ascon_aead_ctx_t* const ctx) +{ + // If there was at least some associated data obtained so far, + // pad it and absorb any content of the buffer. + // Note: this step is performed even if the buffer is now empty because + // a state permutation is required if there was at least some associated + // data absorbed beforehand. + if (ctx->bufstate.flow_state == ASCON_FLOW_AEAD128_80pq_ASSOC_DATA_UPDATED) + { + ctx->bufstate.sponge.x0 ^= bigendian_decode_varlen(ctx->bufstate.buffer, + ctx->bufstate.buffer_len); + ctx->bufstate.sponge.x0 ^= PADDING(ctx->bufstate.buffer_len); + ascon_permutation_6(&ctx->bufstate.sponge); + } + // Application of a constant at end of associated data for domain + // separation. Done always, regardless if there was some associated + // data or not. + ctx->bufstate.sponge.x4 ^= 1U; + ctx->bufstate.buffer_len = 0; +} + +void +ascon_aead_generate_tag(ascon_aead_ctx_t* const ctx, + uint8_t* tag, + size_t tag_len) +{ + while (tag_len > ASCON_AEAD_TAG_MIN_SECURE_LEN) + { + // All bytes before the last 16 + // Note: converting the sponge uint64_t to bytes to then check them as + // uint64_t is required, as the conversion to bytes ensures the + // proper byte order regardless of the platform native endianness. + bigendian_encode_u64(tag, ctx->bufstate.sponge.x3); + bigendian_encode_u64(tag + sizeof(uint64_t), ctx->bufstate.sponge.x4); + ascon_permutation_12(&ctx->bufstate.sponge); + tag_len -= ASCON_AEAD_TAG_MIN_SECURE_LEN; + tag += ASCON_AEAD_TAG_MIN_SECURE_LEN; + } + // The last 16 or fewer bytes (also 0) + uint_fast8_t remaining = (uint_fast8_t) MIN(sizeof(uint64_t), tag_len); + bigendian_encode_varlen(tag, ctx->bufstate.sponge.x3, remaining); + tag += remaining; + // The last 8 or fewer bytes (also 0) + tag_len -= remaining; + bigendian_encode_varlen(tag, ctx->bufstate.sponge.x4, (uint_fast8_t) tag_len); +} + +bool +ascon_aead_is_tag_valid(ascon_aead_ctx_t* const ctx, + const uint8_t* expected_tag, + size_t expected_tag_len) +{ + uint64_t expected_tag_chunk; + bool tags_differ = false; + while (expected_tag_len > ASCON_AEAD_TAG_MIN_SECURE_LEN) + { + // Note: converting the expected tag from uint8_t[] to uint64_t + // for a faster comparison. It has to be decoded explicitly to ensure + // it works the same on all platforms, regardless of endianness. + // Type-punning like `*(uint64_t*) expected_tag` is NOT portable. + // + // Constant time comparison expected vs computed digest chunk, part 1 + expected_tag_chunk = bigendian_decode_u64(expected_tag); + tags_differ |= (expected_tag_chunk ^ ctx->bufstate.sponge.x3); + expected_tag += sizeof(expected_tag_chunk); + expected_tag_len -= sizeof(expected_tag_chunk); + // Constant time comparison expected vs computed digest chunk, part 2 + expected_tag_chunk = bigendian_decode_u64(expected_tag); + tags_differ |= (expected_tag_chunk ^ ctx->bufstate.sponge.x4); + expected_tag += sizeof(expected_tag_chunk); + expected_tag_len -= sizeof(expected_tag_chunk); + // Permute and go to next chunk + ascon_permutation_12(&ctx->bufstate.sponge); + } + // Extract the remaining n most significant bytes of expected/computed tags + size_t remaining = MIN(sizeof(expected_tag_chunk), expected_tag_len); + uint64_t ms_mask = mask_most_signif_bytes((uint_fast8_t) remaining); + expected_tag_chunk = bigendian_decode_varlen(expected_tag, (uint_fast8_t) remaining); + tags_differ |= (expected_tag_chunk ^ (ctx->bufstate.sponge.x3 & ms_mask)); + expected_tag += remaining; + expected_tag_len -= remaining; + remaining = MIN(sizeof(expected_tag_chunk), expected_tag_len); + ms_mask = mask_most_signif_bytes((uint_fast8_t) remaining); + expected_tag_chunk = bigendian_decode_varlen(expected_tag, (uint_fast8_t) remaining); + tags_differ |= (expected_tag_chunk ^ (ctx->bufstate.sponge.x4 & ms_mask)); + return !tags_differ; // True if they are equal +} + +ASCON_API void +ascon_aead_cleanup(ascon_aead_ctx_t* const ctx) +{ + ASCON_ASSERT(ctx != NULL); + // Manual cleanup using volatile pointers to have more assurance the + // cleanup will not be removed by the optimiser. + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.sponge.x0 = 0U; + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.sponge.x1 = 0U; + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.sponge.x2 = 0U; + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.sponge.x3 = 0U; + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.sponge.x4 = 0U; + for (uint_fast8_t i = 0; i < ASCON_DOUBLE_RATE; i++) + { + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.buffer[i] = 0U; + } + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.buffer_len = 0U; + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.flow_state = ASCON_FLOW_CLEANED; + // Clearing also the padding to set the whole context to be all-zeros. + // Makes it easier to check for initialisation and provides a known + // state after cleanup, initialising all memory. + for (uint_fast8_t i = 0U; i < 6U; i++) + { + ((volatile ascon_aead_ctx_t*) ctx)->bufstate.pad[i] = 0U; + } + ((volatile ascon_aead_ctx_t*) ctx)->k0 = 0U; + ((volatile ascon_aead_ctx_t*) ctx)->k1 = 0U; + ((volatile ascon_aead_ctx_t*) ctx)->k2 = 0U; +} diff --git a/lib/LibAscon/src/ascon_buffering.c b/lib/LibAscon/src/ascon_buffering.c new file mode 100644 index 0000000000..1cd30a6882 --- /dev/null +++ b/lib/LibAscon/src/ascon_buffering.c @@ -0,0 +1,160 @@ +/** + * @file + * Implementation of buffering used for the Init-Update-Final paradigm + * of both the AEAD ciphers and hashing. + * + * @license Creative Commons Zero (CC0) 1.0 + * @authors see AUTHORS.md file + */ + +#include +#include +#include "ascon.h" +#include "ascon_internal.h" + +/** + * @internal + * Simplistic clone of memcpy for small arrays. + * + * It should work faster than memcpy for very small amounts of bytes given + * the reduced overhead. + * + * Initially implemented with a while-loop, but it triggers a + * `-Werror=stringop-overflow=` warning in GCC v11. Replaced it with a for-loop + * that does the exact same thing instead to make it work without deactivating + * the warning. We instead let the optimiser figure out the best way to make it + * as fast as possible. + */ +inline static void +small_cpy(uint8_t* const dst, const uint8_t* const src, const size_t amount) +{ + for (uint_fast8_t i = 0U; i < amount; i++) + { + dst[i] = src[i]; + } +} + +ASCON_INLINE uint64_t +bigendian_decode_u64(const uint8_t* const bytes) +{ + uint64_t value = 0; + value |= ((uint64_t) bytes[0]) << 56U; + value |= ((uint64_t) bytes[1]) << 48U; + value |= ((uint64_t) bytes[2]) << 40U; + value |= ((uint64_t) bytes[3]) << 32U; + value |= ((uint64_t) bytes[4]) << 24U; + value |= ((uint64_t) bytes[5]) << 16U; + value |= ((uint64_t) bytes[6]) << 8U; + value |= ((uint64_t) bytes[7]); + return value; +} + +ASCON_INLINE void +bigendian_encode_u64(uint8_t* const bytes, const uint64_t value) +{ + bytes[0] = (uint8_t) (value >> 56U); + bytes[1] = (uint8_t) (value >> 48U); + bytes[2] = (uint8_t) (value >> 40U); + bytes[3] = (uint8_t) (value >> 32U); + bytes[4] = (uint8_t) (value >> 24U); + bytes[5] = (uint8_t) (value >> 16U); + bytes[6] = (uint8_t) (value >> 8U); + bytes[7] = (uint8_t) (value); +} + +ASCON_INLINE uint64_t +bigendian_decode_varlen(const uint8_t* const bytes, const uint_fast8_t n) +{ + uint64_t x = 0; + // Unsigned int should be the fastest unsigned on the machine. + // Using it to avoid warnings about <<-operator with signed value. + for (unsigned int i = 0; i < n; i++) + { + x |= ((uint64_t) bytes[i]) << (56U - 8U * i); + } + return x; +} + +ASCON_INLINE void +bigendian_encode_varlen(uint8_t* const bytes, const uint64_t x, const uint_fast8_t n) +{ + // Unsigned int should be the fastest unsigned on the machine. + // Using it to avoid warnings about <<-operator with signed value. + for (unsigned int i = 0; i < n; i++) + { + bytes[i] = (uint8_t) (x >> (56U - 8U * i)); + } +} + +uint64_t +mask_most_signif_bytes(uint_fast8_t n) +{ + uint64_t x = 0; + // Unsigned int should be the fastest unsigned on the machine. + // Using it to avoid warnings about <<-operator with signed value. + for (unsigned int i = 0; i < n; i++) + { + x |= 0xFFULL << (56U - 8U * i); + } + return x; +} + +size_t +buffered_accumulation(ascon_bufstate_t* const ctx, + uint8_t* data_out, + const uint8_t* data_in, + absorb_fptr absorb, + size_t data_in_len, + const uint8_t rate) +{ + size_t fresh_out_bytes = 0; + if (ctx->buffer_len > 0) + { + // There is data in the buffer already. + // Place as much as possible of the new data into the buffer. + const uint_fast8_t space_in_buf = (uint_fast8_t) (rate - ctx->buffer_len); + const uint_fast8_t into_buffer = (uint_fast8_t) MIN(space_in_buf, data_in_len); + small_cpy(&ctx->buffer[ctx->buffer_len], data_in, into_buffer); + ctx->buffer_len = (uint8_t) (ctx->buffer_len + into_buffer); + data_in += into_buffer; + data_in_len -= into_buffer; + if (ctx->buffer_len == rate) + { + // The buffer was filled completely, thus absorb it. + absorb(&ctx->sponge, data_out, ctx->buffer); + ctx->buffer_len = 0; + data_out += rate; + fresh_out_bytes += rate; + } + else + { + // Do nothing. + // The buffer contains some data, but it's not full yet + // and there is no more data in this update call. + // Keep it cached for the next update call or the digest call. + } + } + else + { + // Do nothing. + // The buffer contains no data, because this is the first update call + // or because the last update had no less-than-a-block trailing data. + } + // Absorb remaining data (if any) one block at the time. + while (data_in_len >= rate) + { + absorb(&ctx->sponge, data_out, data_in); + data_out += rate; + data_in += rate; + data_in_len -= rate; + fresh_out_bytes += rate; + } + // If there is any remaining less-than-a-block data to be absorbed, + // cache it into the buffer for the next update call or digest call. + if (data_in_len > 0) + { + small_cpy(ctx->buffer, data_in, data_in_len); + ctx->buffer_len = (uint8_t) data_in_len; + } + return fresh_out_bytes; +} diff --git a/lib/LibAscon/src/ascon_internal.h b/lib/LibAscon/src/ascon_internal.h new file mode 100644 index 0000000000..950dc7952a --- /dev/null +++ b/lib/LibAscon/src/ascon_internal.h @@ -0,0 +1,363 @@ +/** + * @file + * LibAscon internal header file. + * + * Common code (mostly the sponge state permutations and conversion utilities) + * applied during encryption, decryption and hashing. + * + * @license Creative Commons Zero (CC0) 1.0 + * @authors see AUTHORS.md file + */ + +#ifndef ASCON_INTERNAL_H +#define ASCON_INTERNAL_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "ascon.h" + +#if defined(DEBUG) || defined(MINSIZEREL) || defined(ASCON_WINDOWS) +// Do not inline in debug mode or when sparing space. +// Inlining on MSVC creates linking issues: some inlined functions cannot +// be resolved. +#define ASCON_INLINE +#else +#define ASCON_INLINE inline +#endif + +/* Definitions of the initialisation vectors used to initialise the sponge + * state for AEAD and the two types of hashing functions. */ +/** + * @internal + * Initialisation vector for the Ascon128 AEAD cipher. + * + * Equivalent to the binary concatenation of `k || r || a || b || 0^(160−k)` + * (from most significant bit on the left to the least significant bit on the + * right) where: + * + * - `k` is the key length in bits: 128 = 0x80 + * - `r` is the AEAD rate in bits: 64 = 0x40 + * - `a` is the amount of rounds during the a-permutation: 12 = 0x0C + * - `b` is the amount of rounds during the b-permutation: 6 = 0x06 + * - right-padded with zeros until we reach 64 bits of size + */ +#define ASCON_IV_AEAD128 0x80400c0600000000ULL + +/** + * @internal + * Initialisation vector for the Ascon128a AEAD cipher. + * + * Equivalent to the binary concatenation of `k || r || a || b || 0^(160−k)` + * (from most significant bit on the left to the least significant bit on the + * right) where: + * + * - `k` is the key length in bits: 128 = 0x80 + * - `r` is the AEAD rate in bits: 128 = 0x80 + * - `a` is the amount of rounds during the a-permutation: 12 = 0x0C + * - `b` is the amount of rounds during the b-permutation: 8 = 0x08 + * - right-padded with zeros until we reach 64 bits of size + */ +#define ASCON_IV_AEAD128a 0x80800c0800000000ULL + +/** + * @internal + * Initialisation vector for the Ascon80pq AEAD cipher. + * + * Equivalent to the binary concatenation of `k || r || a || b || 0^(160−k)` + * (from most significant bit on the left to the least significant bit on the + * right) where: + * + * - `k` is the key length in bits: 160 = 0xa0 + * - `r` is the AEAD rate in bits: 64 = 0x40 + * - `a` is the amount of rounds during the a-permutation: 12 = 0x0C + * - `b` is the amount of rounds during the b-permutation: 6 = 0x06 + * - right-padded with zeros until we reach 64 bits of size + */ +#define ASCON_IV_AEAD80pq 0xa0400c0600000000ULL + +/** + * @internal + * Initialisation vector for the Ascon-Hash hashing function. + * + * Equivalent to the binary concatenation of `0^8 || r || a || a − b || h` + * (from most significant bit on the left to the least significant bit on the + * right) where: + * + * - left-padding with eight zero-bits until we reach 64 bits of size + * - `r` is the hash rate in bits: 64 = 0x40 + * - `a` is the amount of rounds during the a-permutation: 12 = 0x0C + * - `b` is the amount of rounds during the b-permutation: 12 = 0x0C + * - `h` is the digest length in bits expressed as `uint32_t`: 256 = 0x00000100 + */ +#define ASCON_IV_HASH 0x00400c0000000100ULL + +/** + * @internal + * Initialisation vector for the Ascon-Hasha hashing function. + * + * Equivalent to the binary concatenation of `0^8 || r || a || a − b || h` + * (from most significant bit on the left to the least significant bit on the + * right) where: + * + * - left-padding with eight zero-bits until we reach 64 bits of size + * - `r` is the hash rate in bits: 64 = 0x40 + * - `a` is the amount of rounds during the a-permutation: 12 = 0x0C + * - `b` is the amount of rounds during the b-permutation: 8 = 0x08 + * - `h` is the digest length in bits expressed as `uint32_t`: 256 = 0x00000100 + */ +#define ASCON_IV_HASHa 0x00400c0400000100ULL + +/** + * @internal + * Initialisation vector for the Ascon-XOF hashing function. + * + * Equivalent to the binary concatenation of `0^8 || r || a || a − b || h` + * (from most significant bit on the left to the least significant bit on the + * right) where: + * + * - left-padding with eight zero-bits until we reach 64 bits of size + * - `r` is the hash rate in bits: 64 = 0x40 + * - `a` is the amount of rounds during the a-permutation: 12 = 0x0C + * - `b` is the amount of rounds during the b-permutation: 12 = 0x0C + * - `h` is the digest length in bits expressed as `uint32_t`: 0 = 0x00000000, + * here being zero, because of unlimited length + */ +#define ASCON_IV_XOF 0x00400c0000000000ULL + +/** + * @internal + * Initialisation vector for the Ascon-XOFa hashing function. + * + * Equivalent to the binary concatenation of `0^8 || r || a || a − b || h` + * (from most significant bit on the left to the least significant bit on the + * right) where: + * + * - left-padding with eight zero-bits until we reach 64 bits of size + * - `r` is the hash rate in bits: 64 = 0x40 + * - `a` is the amount of rounds during the a-permutation: 12 = 0x0C + * - `b` is the amount of rounds during the b-permutation: 8 = 0x08 + * - `h` is the digest length in bits expressed as `uint32_t`: 0 = 0x00000000, + * here being zero, because of unlimited length + */ +#define ASCON_IV_XOFa 0x00400c0400000000ULL + +/** + * @internal + * Applies 0b1000...000 right-side padding to a uint8_t[8] array of + * `bytes` filled elements.. + */ +#define PADDING(bytes) (0x80ULL << (56U - 8U * ((unsigned int) (bytes)))) + +/** + * @internal + * Simple minimum out of 2 arguments. + */ +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/** + * @internal + * States used to understand which function of the API was called before + * for the input assertions and to known if the associated data has been + * updated or not. + * @see #ASCON_INPUT_ASSERTS + */ +typedef enum +{ + ASCON_FLOW_CLEANED = 0, + ASCON_FLOW_HASH_INITIALISED, + ASCON_FLOW_HASH_UPDATED, + ASCON_FLOW_AEAD128_80pq_INITIALISED, + ASCON_FLOW_AEAD128_80pq_ASSOC_DATA_UPDATED, + ASCON_FLOW_AEAD128_80pq_ENCRYPT_UPDATED, + ASCON_FLOW_AEAD128_80pq_DECRYPT_UPDATED, + ASCON_FLOW_AEAD128a_INITIALISED, + ASCON_FLOW_AEAD128a_ASSOC_DATA_UPDATED, + ASCON_FLOW_AEAD128a_ENCRYPT_UPDATED, + ASCON_FLOW_AEAD128a_DECRYPT_UPDATED, + ASCON_FLOW_HASHA_INITIALISED, + ASCON_FLOW_HASHA_UPDATED, +} ascon_flow_t; + +/** @internal Decodes an uint64_t from a big-endian encoded array of 8 bytes. */ +uint64_t +bigendian_decode_u64(const uint8_t* bytes); + +/** @internal Decodes an uint64_t from a big-endian encoded array of N bytes. + * The N bytes are interpreted as the N most significant bytes of the integer, + * the unspecified bytes are set to 0. */ +uint64_t +bigendian_decode_varlen(const uint8_t* bytes, uint_fast8_t n); + +/** @internal Encodes an uint64_t into a big-endian encoded array of 8 bytes. */ +void +bigendian_encode_u64(uint8_t* bytes, uint64_t value); + +/** @internal Encodes an uint64_t into a big-endian encoded array of N bytes. + * The N most significant bytes of the integer are written into the first N + * bytes of the array, the unspecified bytes are not written. */ +void +bigendian_encode_varlen(uint8_t* bytes, uint64_t x, uint_fast8_t n); + +/** + * @internal + * Creates a mask to extract the n most significant bytes of a uint64_t. + * + * Examples: + * + * - `n == 1` returns `FF00 0000 0000 0000` (spaces are just for readability) + * - `n == 5` returns `FFFF FFFF FF00 0000` + */ +uint64_t +mask_most_signif_bytes(uint_fast8_t n); + +/** + * @internal + * Performs one permutation round on the Ascon sponge for the given round + * constant. + * + * @warning + * Do not use directly! Use ascon_permutation_12(), ascon_permutation_8(), + * ascon_permutation_6() instead. + * + * Although this function is never used outside the file where it is + * defined, it is NOT marked as static, and it is declared globally + * as it is generally inlined the functions using it to increase the + * performance. Inlining static functions into functions used outside their + * file leads to compilation errors: "error: static function 'ascon_round' is + * used in an inline function with external linkage + * [-Werror,-Wstatic-in-inline]". + */ +void +ascon_round(ascon_sponge_t* sponge, uint_fast8_t round_const); + +/** + * @internal + * Ascon sponge permutation with 12 rounds. + */ +void +ascon_permutation_12(ascon_sponge_t* sponge); + +/** + * @internal + * Ascon sponge permutation with 8 rounds. + */ +void +ascon_permutation_8(ascon_sponge_t* sponge); + +/** + * @internal + * Ascon sponge permutation with 6 rounds. + */ +void +ascon_permutation_6(ascon_sponge_t* sponge); + +/** + * @internal + * Function pointer representing any Ascon sponge permutation. + */ +typedef void (* permutation_fptr)(ascon_sponge_t*); + +/** + * @internal + * Initialises the AEAD128 or AEAD128a online processing. + */ +void +ascon_aead_init(ascon_aead_ctx_t* ctx, + const uint8_t* key, + const uint8_t* nonce, + uint64_t iv); + +/** + * @internal + * Handles the finalisation of the associated data before any plaintext or + * ciphertext is being processed for Ascon128 and Ascon80pq + * + * MUST be called ONLY once! In other words, when + * ctx->bufstate.assoc_data_state == ASCON_FLOW_ASSOC_DATA_FINALISED + * then it MUST NOT be called anymore. + * + * It handles both the case when some or none associated data was given. + */ +void +ascon_aead128_80pq_finalise_assoc_data(ascon_aead_ctx_t* ctx); + +/** + * @internal + * Generates an arbitrary-length tag from a finalised state for all AEAD + * ciphers. + * + * MUST be called ONLY when all AD and PT/CT is absorbed and state is + * prepared for tag generation. + */ +void +ascon_aead_generate_tag(ascon_aead_ctx_t* ctx, + uint8_t* tag, + size_t tag_len); + +/** + * @internal + * Generates the arbitrary-length tag one chunk at the time and compares + * it to the tag that came with the ciphertext. + * + * MUST be called ONLY when all AD and PT/CT is absorbed and the state is + * prepared for tag generation. Consumes a fixed amount of stack memory. + */ +bool +ascon_aead_is_tag_valid(ascon_aead_ctx_t* ctx, + const uint8_t* expected_tag, + size_t expected_tag_len); + +/** + * @internal + * Function pointer representing the operation run by the + * buffered_accumulation() when ASCON_RATE bytes ara available in the buffer to + * be absorbed. + * + * @param[in, out] sponge the sponge state to absorb data into. + * @param[out] data_out optional outgoing data from the sponge, which happens + * during encryption or decryption, but not during hashing. + * @param[in] data_in the input data to be absorbed by the sponge. + */ +typedef void (* absorb_fptr)(ascon_sponge_t* sponge, + uint8_t* data_out, + const uint8_t* data_in); + +/** + * @internal + * Buffers any input data into the bufstate and on accumulation of ASCON_RATE + * bytes, runs the absorb function to process them. + * + * This function is used by the AEAD and hash implementations to enable + * the Init-Update-Final paradigm. The update functions pass the absorb_fptr + * function specific to them, while this function is the framework handling the + * accumulation of data until the proper amount is reached. + * + * It is not used during the Final step, as that requires padding and special + * additional operations such as tag/digest generation. + * + * @param[in, out] ctx the sponge and the buffer to accumulate data in + * @param[out] data_out optional output data squeezed from the sponge + * @param[in] data_in input data to be absorbed by the sponge + * @param[in] absorb function that handles the absorption and optional squeezing + * of the sponge + * @param[in] data_in_len length of the \p data_in in bytes + * @param[in] rate buffer size, i.e. number of accumulated bytes after which + * an absorption is required + * @return number of bytes written into \p data_out + */ +size_t +buffered_accumulation(ascon_bufstate_t* ctx, + uint8_t* data_out, + const uint8_t* data_in, + absorb_fptr absorb, + size_t data_in_len, + uint8_t rate); + +#ifdef __cplusplus +} +#endif + +#endif /* ASCON_INTERNAL_H */ diff --git a/lib/LibAscon/src/ascon_permutations.c b/lib/LibAscon/src/ascon_permutations.c new file mode 100644 index 0000000000..d9225550c1 --- /dev/null +++ b/lib/LibAscon/src/ascon_permutations.c @@ -0,0 +1,123 @@ +/** + * @file + * Core cryptographic operations, i.e. permutations of the sponge state. + * + * @license Creative Commons Zero (CC0) 1.0 + * @authors see AUTHORS.md file + */ + +#include "ascon.h" +/* Linter warnings about #include "ascon_internal.h" being unused are WRONG. + * If you do not include the header, the linker cannot find the references. */ +#include "ascon_internal.h" + + +// 12-round permutation starts here +#define ROUND_CONSTANT_01 0xF0 +#define ROUND_CONSTANT_02 0xE1 +#define ROUND_CONSTANT_03 0xD2 +#define ROUND_CONSTANT_04 0xC3 + +// 8-round permutation starts here +#define ROUND_CONSTANT_05 0xB4 +#define ROUND_CONSTANT_06 0xA5 + +// 6-round permutation starts here +#define ROUND_CONSTANT_07 0x96 +#define ROUND_CONSTANT_08 0x87 +#define ROUND_CONSTANT_09 0x78 +#define ROUND_CONSTANT_10 0x69 +#define ROUND_CONSTANT_11 0x5A +#define ROUND_CONSTANT_12 0x4B + +/** Bit-shift and rotation of a uint64_t to the right by n bits. */ +#define ASCON_ROTR64(x, n) (((x) << (64U - (n)) ) | ((x) >> (n))) + +/** + * @internal + * Performs one permutation round on the Ascon sponge for the given round + * constant. + * + * Although this function is never used outside this file, + * it is NOT marked as static, as it is generally inline in the functions + * using it to increase the performance. Inlining static functions into + * functions used outside this file leads to compilation errors: + * "error: static function 'ascon_round' is used in an inline function with + * external linkage `[-Werror,-Wstatic-in-inline]`". + */ +ASCON_INLINE void +ascon_round(ascon_sponge_t* sponge, + const uint_fast8_t round_const) +{ + // addition of round constant + sponge->x2 ^= round_const; + // substitution layer + sponge->x0 ^= sponge->x4; + sponge->x4 ^= sponge->x3; + sponge->x2 ^= sponge->x1; + // start of keccak s-box + const ascon_sponge_t temp = { + .x0 = (~sponge->x0) & sponge->x1, + .x1 = (~sponge->x1) & sponge->x2, + .x2 = (~sponge->x2) & sponge->x3, + .x3 = (~sponge->x3) & sponge->x4, + .x4 = (~sponge->x4) & sponge->x0, + }; + sponge->x0 ^= temp.x1; + sponge->x1 ^= temp.x2; + sponge->x2 ^= temp.x3; + sponge->x3 ^= temp.x4; + sponge->x4 ^= temp.x0; + // end of keccak s-box + sponge->x1 ^= sponge->x0; + sponge->x0 ^= sponge->x4; + sponge->x3 ^= sponge->x2; + sponge->x2 = ~sponge->x2; + // linear diffusion layer + sponge->x0 ^= ASCON_ROTR64(sponge->x0, 19) ^ ASCON_ROTR64(sponge->x0, 28); + sponge->x1 ^= ASCON_ROTR64(sponge->x1, 61) ^ ASCON_ROTR64(sponge->x1, 39); + sponge->x2 ^= ASCON_ROTR64(sponge->x2, 1) ^ ASCON_ROTR64(sponge->x2, 6); + sponge->x3 ^= ASCON_ROTR64(sponge->x3, 10) ^ ASCON_ROTR64(sponge->x3, 17); + sponge->x4 ^= ASCON_ROTR64(sponge->x4, 7) ^ ASCON_ROTR64(sponge->x4, 41); +} + +ASCON_INLINE void +ascon_permutation_12(ascon_sponge_t* const sponge) +{ + ascon_round(sponge, ROUND_CONSTANT_01); + ascon_round(sponge, ROUND_CONSTANT_02); + ascon_round(sponge, ROUND_CONSTANT_03); + ascon_round(sponge, ROUND_CONSTANT_04); + ascon_round(sponge, ROUND_CONSTANT_05); + ascon_round(sponge, ROUND_CONSTANT_06); + ascon_round(sponge, ROUND_CONSTANT_07); + ascon_round(sponge, ROUND_CONSTANT_08); + ascon_round(sponge, ROUND_CONSTANT_09); + ascon_round(sponge, ROUND_CONSTANT_10); + ascon_round(sponge, ROUND_CONSTANT_11); + ascon_round(sponge, ROUND_CONSTANT_12); +} + +ASCON_INLINE void +ascon_permutation_8(ascon_sponge_t* const sponge) +{ + ascon_round(sponge, ROUND_CONSTANT_05); + ascon_round(sponge, ROUND_CONSTANT_06); + ascon_round(sponge, ROUND_CONSTANT_07); + ascon_round(sponge, ROUND_CONSTANT_08); + ascon_round(sponge, ROUND_CONSTANT_09); + ascon_round(sponge, ROUND_CONSTANT_10); + ascon_round(sponge, ROUND_CONSTANT_11); + ascon_round(sponge, ROUND_CONSTANT_12); +} + +ASCON_INLINE void +ascon_permutation_6(ascon_sponge_t* const sponge) +{ + ascon_round(sponge, ROUND_CONSTANT_07); + ascon_round(sponge, ROUND_CONSTANT_08); + ascon_round(sponge, ROUND_CONSTANT_09); + ascon_round(sponge, ROUND_CONSTANT_10); + ascon_round(sponge, ROUND_CONSTANT_11); + ascon_round(sponge, ROUND_CONSTANT_12); +} diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 0548c9073d..8a2de7a9e5 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -46,6 +46,11 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { if (pkt->path_len < MAX_PATH_SIZE) { + // TRACE packet minimum: trace_tag(4) + auth_code(4) + flags(1) = 9 bytes + if (pkt->payload_len < 9) { + MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete TRACE packet", getLogDateTime()); + return ACTION_RELEASE; + } uint8_t i = 0; uint32_t trace_tag; memcpy(&trace_tag, &pkt->payload[i], 4); i += 4; @@ -56,9 +61,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t len = pkt->payload_len - i; uint8_t offset = pkt->path_len << path_sz; + uint8_t hash_size = 1 << path_sz; if (offset >= len) { // TRACE has reached end of given path onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len); - } else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { + } else if (i + offset + hash_size <= pkt->payload_len && self_id.isHashMatch(&pkt->payload[i + offset], hash_size) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { // append SNR (Not hash!) pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4); @@ -80,10 +86,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { // check for 'early received' ACK if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { - int i = 0; - uint32_t ack_crc; - memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; - if (i <= pkt->payload_len) { + if (pkt->payload_len >= 4) { + uint32_t ack_crc; + memcpy(&ack_crc, &pkt->payload[0], 4); onAckRecv(pkt, ack_crc); } } @@ -115,12 +120,11 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { switch (pkt->getPayloadType()) { case PAYLOAD_TYPE_ACK: { - int i = 0; - uint32_t ack_crc; - memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; - if (i > pkt->payload_len) { + if (pkt->payload_len < 4) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete ACK packet", getLogDateTime()); } else if (!_tables->hasSeen(pkt)) { + uint32_t ack_crc; + memcpy(&ack_crc, &pkt->payload[0], 4); onAckRecv(pkt, ack_crc); action = routeRecvPacket(pkt); } @@ -134,8 +138,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t dest_hash = pkt->payload[i++]; uint8_t src_hash = pkt->payload[i++]; - uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data - if (i + CIPHER_MAC_SIZE >= pkt->payload_len) { + uint8_t* encryptedData = &pkt->payload[i]; // encrypted data (AES: MAC + ciphertext, Ascon: counter + ciphertext + tag) + int encrypted_len = pkt->payload_len - i; + + if (encrypted_len < CIPHER_MAC_SIZE) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); } else if (!_tables->hasSeen(pkt)) { // NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins. @@ -151,10 +157,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t secret[PUB_KEY_SIZE]; getPeerSharedSecret(secret, j); - // decrypt, checking MAC is valid + // Unified decryption: tries Ascon first, falls back to legacy AES-ECB+HMAC uint8_t data[MAX_PACKET_PAYLOAD]; - int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + bool was_ascon = false; + int len = Utils::decryptAuto(secret, data, encryptedData, encrypted_len, &was_ascon); + if (len > 0) { // success! + // Notify subclass of detected crypto capability (for auto-upgrade to Ascon) + onPeerAsconCapabilityDetected(j, was_ascon); + if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { int k = 0; uint8_t path_len = data[k++]; @@ -164,17 +175,23 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!) if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) { if (pkt->isRouteFlood()) { - // send a reciprocal return path to sender, but send DIRECTLY! - mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0); + // send a reciprocal return path to sender using same crypto they used, but send DIRECTLY! + mesh::Packet* rpath = createPathReturn(&src_hash, secret, pkt->path, pkt->path_len, 0, NULL, 0, was_ascon); if (rpath) sendDirect(rpath, path, path_len, 500); } } } else { onPeerDataRecv(pkt, pkt->getPayloadType(), j, secret, data, len); } + // SECURITY: Clear sensitive data from stack before breaking + memset(data, 0, sizeof(data)); + memset(secret, 0, sizeof(secret)); found = true; break; } + // SECURITY: Clear buffers even if decryption failed + memset(data, 0, sizeof(data)); + memset(secret, 0, sizeof(secret)); } if (found) { pkt->markDoNotRetransmit(); // packet was for this node, so don't retransmit @@ -191,8 +208,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t dest_hash = pkt->payload[i++]; uint8_t* sender_pub_key = &pkt->payload[i]; i += PUB_KEY_SIZE; - uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data - if (i + 2 >= pkt->payload_len) { + uint8_t* encryptedData = &pkt->payload[i]; // encrypted data (AES: MAC + ciphertext, Ascon: counter + ciphertext + tag) + int encrypted_len = pkt->payload_len - i; + + if (encrypted_len < CIPHER_MAC_SIZE + 1) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); } else if (!_tables->hasSeen(pkt)) { if (self_id.isHashMatch(&dest_hash)) { @@ -201,25 +220,32 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t secret[PUB_KEY_SIZE]; self_id.calcSharedSecret(secret, sender); - // decrypt, checking MAC is valid + // Unified decryption: tries Ascon first, falls back to legacy AES-ECB+HMAC uint8_t data[MAX_PACKET_PAYLOAD]; - int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i); + bool was_ascon = false; + int len = Utils::decryptAuto(secret, data, encryptedData, encrypted_len, &was_ascon); + if (len > 0) { // success! - onAnonDataRecv(pkt, secret, sender, data, len); + onAnonDataRecv(pkt, secret, sender, data, len, was_ascon); pkt->markDoNotRetransmit(); } + // SECURITY: Clear sensitive data from stack + memset(data, 0, sizeof(data)); + memset(secret, 0, sizeof(secret)); } action = routeRecvPacket(pkt); } break; } - case PAYLOAD_TYPE_GRP_DATA: + case PAYLOAD_TYPE_GRP_DATA: case PAYLOAD_TYPE_GRP_TXT: { int i = 0; uint8_t channel_hash = pkt->payload[i++]; - uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data - if (i + 2 >= pkt->payload_len) { + uint8_t* encryptedData = &pkt->payload[i]; // encrypted data (AES: MAC + ciphertext, Ascon: counter + ciphertext + tag) + int encrypted_len = pkt->payload_len - i; + + if (encrypted_len < 2) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); } else if (!_tables->hasSeen(pkt)) { // scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM) @@ -227,13 +253,18 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { int num = searchChannelsByHash(&channel_hash, channels, 4); // for each matching channel, try to decrypt data for (int j = 0; j < num; j++) { - // decrypt, checking MAC is valid + // Unified decryption: tries Ascon first, falls back to legacy AES-ECB+HMAC uint8_t data[MAX_PACKET_PAYLOAD]; - int len = Utils::MACThenDecrypt(channels[j].secret, data, macAndData, pkt->payload_len - i); + int len = Utils::decryptAuto(channels[j].secret, data, encryptedData, encrypted_len); + if (len > 0) { // success! onGroupDataRecv(pkt, pkt->getPayloadType(), channels[j], data, len); + // SECURITY: Clear sensitive data from stack before breaking + memset(data, 0, sizeof(data)); break; } + // SECURITY: Clear buffer even if decryption failed + memset(data, 0, sizeof(data)); } action = routeRecvPacket(pkt); } @@ -430,23 +461,38 @@ Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, siz return packet; } -#define MAX_COMBINED_PATH (MAX_PACKET_PAYLOAD - 2 - CIPHER_BLOCK_SIZE) - -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) { +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) { uint8_t dest_hash[PATH_HASH_SIZE]; dest.copyHashTo(dest_hash); - return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len); + return createPathReturn(dest_hash, secret, path, path_len, extra_type, extra, extra_len, use_ascon); } -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) { - if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!! +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) { + // Plaintext layout: [path_len (1)] [path (path_len)] [extra_type (1)] [extra (extra_len)] + // Or if no extra: [dummy_type (1)] [rand (4)] + const int plain_len = 1 + (int)path_len + ((extra_len > 0) ? (1 + (int)extra_len) : (1 + 4)); + + // Payload prefix: dest_hash (1) + src_hash (1) + const int prefix_len = 2; + + // Check size based on encryption mode + if (use_ascon) { + // V2: prefix + counter + ciphertext + tag + if (prefix_len + ASCON_COUNTER_SIZE + plain_len + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL; + } else { + // V1: prefix + MAC + ciphertext (rounded up to 16) + int enc_len = ((plain_len + 15) / 16) * 16; + if (prefix_len + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL; + } Packet* packet = obtainNewPacket(); if (packet == NULL) { MESH_DEBUG_PRINTLN("%s Mesh::createPathReturn(): error, packet pool empty", getLogDateTime()); return NULL; } - packet->header = (PAYLOAD_TYPE_PATH << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later + + // Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters + packet->header = (PAYLOAD_TYPE_PATH << PH_TYPE_SHIFT); int len = 0; memcpy(&packet->payload[len], dest_hash, PATH_HASH_SIZE); len += PATH_HASH_SIZE; // dest hash @@ -467,7 +513,12 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, getRNG()->random(&data[data_len], 4); data_len += 4; } - len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + // Encrypt based on peer capability + if (use_ascon) { + len += Utils::encryptAscon(secret, &packet->payload[len], data, data_len); + } else { + len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + } } packet->payload_len = len; @@ -475,9 +526,19 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, return packet; } -Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) { +Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len, bool use_ascon) { if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE) { - if (data_len + CIPHER_MAC_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; + // Payload prefix: dest_hash (1) + src_hash (1) + const int prefix_len = 2; + // Check size based on encryption mode + if (use_ascon) { + // V2: prefix + counter + ciphertext + tag + if (prefix_len + (int)data_len + ASCON_COUNTER_SIZE + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL; + } else { + // V1: prefix + MAC + ciphertext (rounded up to 16) + int enc_len = (((int)data_len + 15) / 16) * 16; + if (prefix_len + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL; + } } else { return NULL; // invalid type } @@ -487,21 +548,37 @@ Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* MESH_DEBUG_PRINTLN("%s Mesh::createDatagram(): error, packet pool empty", getLogDateTime()); return NULL; } - packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later + + // Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters + packet->header = (type << PH_TYPE_SHIFT); int len = 0; len += dest.copyHashTo(&packet->payload[len]); // dest hash len += self_id.copyHashTo(&packet->payload[len]); // src hash - len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + + // Encrypt based on peer capability + if (use_ascon) { + len += Utils::encryptAscon(secret, &packet->payload[len], data, data_len); + } else { + len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + } packet->payload_len = len; return packet; } -Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) { +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) { if (type == PAYLOAD_TYPE_ANON_REQ) { - if (data_len + 1 + PUB_KEY_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; + // Check size based on encryption mode + if (use_ascon) { + // V2: dest_hash + pub_key + counter + ciphertext + tag + if (data_len + 1 + PUB_KEY_SIZE + ASCON_COUNTER_SIZE + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL; + } else { + // V1: dest_hash + pub_key + MAC + ciphertext (rounded up to 16) + int enc_len = ((data_len + 15) / 16) * 16; + if (1 + PUB_KEY_SIZE + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL; + } } else { return NULL; // invalid type } @@ -511,7 +588,9 @@ Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, cons MESH_DEBUG_PRINTLN("%s Mesh::createAnonDatagram(): error, packet pool empty", getLogDateTime()); return NULL; } - packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later + + // Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters + packet->header = (type << PH_TYPE_SHIFT); int len = 0; if (type == PAYLOAD_TYPE_ANON_REQ) { @@ -520,27 +599,52 @@ Packet* Mesh::createAnonDatagram(uint8_t type, const LocalIdentity& sender, cons } else { // FUTURE: } - len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + + // Encrypt based on peer capability + if (use_ascon) { + len += Utils::encryptAscon(secret, &packet->payload[len], data, data_len); + } else { + len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len); + } packet->payload_len = len; return packet; } -Packet* Mesh::createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len) { +Packet* Mesh::createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len, bool use_ascon) { if (!(type == PAYLOAD_TYPE_GRP_TXT || type == PAYLOAD_TYPE_GRP_DATA)) return NULL; // invalid type - if (data_len + 1 + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; // too long + + // Payload prefix: channel_hash (PATH_HASH_SIZE, currently 1) + const int prefix_len = PATH_HASH_SIZE; + // Check size based on encryption mode + if (use_ascon) { + // V2: prefix + counter + ciphertext + tag + if (prefix_len + (int)data_len + ASCON_COUNTER_SIZE + ASCON_TAG_SIZE > MAX_PACKET_PAYLOAD) return NULL; + } else { + // V1: prefix + MAC + ciphertext (rounded up to 16) + int enc_len = (((int)data_len + 15) / 16) * 16; + if (prefix_len + CIPHER_MAC_SIZE + enc_len > MAX_PACKET_PAYLOAD) return NULL; + } Packet* packet = obtainNewPacket(); if (packet == NULL) { MESH_DEBUG_PRINTLN("%s Mesh::createGroupDatagram(): error, packet pool empty", getLogDateTime()); return NULL; } - packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later + + // Always use PAYLOAD_VER_1 header for backwards compatibility with old repeaters + packet->header = (type << PH_TYPE_SHIFT); int len = 0; memcpy(&packet->payload[len], channel.hash, PATH_HASH_SIZE); len += PATH_HASH_SIZE; - len += Utils::encryptThenMAC(channel.secret, &packet->payload[len], data, data_len); + + // Encrypt based on channel flag or explicit request + if (use_ascon) { + len += Utils::encryptAscon(channel.secret, &packet->payload[len], data, data_len); + } else { + len += Utils::encryptThenMAC(channel.secret, &packet->payload[len], data, data_len); + } packet->payload_len = len; diff --git a/src/Mesh.h b/src/Mesh.h index 00f7ed00f4..904b0aeda6 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -4,10 +4,14 @@ namespace mesh { +// Channel flags +#define CHANNEL_FLAG_ASCON 0x01 // Use Ascon-128 AEAD encryption for this channel + class GroupChannel { public: uint8_t hash[PATH_HASH_SIZE]; uint8_t secret[PUB_KEY_SIZE]; + uint8_t flags; // CHANNEL_FLAG_* bits }; /** @@ -83,6 +87,14 @@ class Mesh : public Dispatcher { */ virtual void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } + /** + * \brief Called when a peer's Ascon (V2) encryption capability is detected from an incoming packet. + * Subclasses can override to update peer capability flags for future sends. + * \param peer_idx index of peer, [0..n) where n is what searchPeersByHash() returned + * \param supports_ascon true if peer sent V2-encrypted packet + */ + virtual void onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) { } + /** * \brief A (now decrypted) data packet has been received (by a known peer). * NOTE: these can be received multiple times (per sender/msg-id), via different routes @@ -125,8 +137,9 @@ class Mesh : public Dispatcher { * NOTE: these can be received multiple times (per sender/contents), via different routes * \param secret ECDH shared secret * \param sender public key provided by sender + * \param was_ascon true if Ascon decryption was used, false if legacy */ - virtual void onAnonDataRecv(Packet* packet, const uint8_t* secret, const Identity& sender, uint8_t* data, size_t len) { } + virtual void onAnonDataRecv(Packet* packet, const uint8_t* secret, const Identity& sender, uint8_t* data, size_t len, bool was_ascon) { } /** * \brief A path TO 'sender' has been received. (also with optional 'extra' data encoded) @@ -182,13 +195,13 @@ class Mesh : public Dispatcher { RTCClock* getRTCClock() const { return _rtc; } Packet* createAdvert(const LocalIdentity& id, const uint8_t* app_data=NULL, size_t app_data_len=0); - Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len); - Packet* createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len); - Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len); + Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len, bool use_ascon=true); + Packet* 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=true); + Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len, bool use_ascon=true); Packet* createAck(uint32_t ack_crc); Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining); - 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); - 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); + 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, bool use_ascon=true); + 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, bool use_ascon=true); Packet* createRawData(const uint8_t* data, size_t len); Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0); Packet* createControlData(const uint8_t* data, size_t len); diff --git a/src/MeshCore.h b/src/MeshCore.h index f194cdeb43..b8df8f56ac 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -12,10 +12,37 @@ #define CIPHER_KEY_SIZE 16 #define CIPHER_BLOCK_SIZE 16 -// V1 +// V1 (AES-ECB + HMAC) - Legacy encryption #define CIPHER_MAC_SIZE 2 #define PATH_HASH_SIZE 1 +// Ascon-128 AEAD encryption with per-packet key derivation +// +// Design goals: +// 1. Minimize airtime (8 bytes overhead: 4-byte counter + 4-byte tag) +// 2. Strong security through per-packet rekeying +// 3. Simple try-decrypt fallback (no capability flags needed) +// +// Per-packet key derivation: +// packet_key = HMAC-SHA256(shared_secret, counter)[0:16] +// +// This enables a short 4-byte tag because: +// - Each message uses a unique derived key +// - Attacker can't accumulate forgery attempts across messages +// - At LoRa's 500ms/packet, brute forcing 2^32 attempts takes 68 years +// +// Counter: 4 bytes, initialized to random value at boot, increments per packet. +// Random boot offset prevents counter reuse across reboots when RTC is unreliable. +// +// Backwards compatibility: +// - Try Ascon decrypt first, fall back to legacy AES-ECB+HMAC on failure +// - Old clients silently drop Ascon packets (tag check fails) +#define ASCON_KEY_SIZE 16 // Ascon-128 uses 128-bit key +#define ASCON_NONCE_SIZE 16 // Ascon-128 uses 128-bit nonce (internal) +#define ASCON_COUNTER_SIZE 4 // Transmitted counter (random boot offset + sequence) +#define ASCON_TAG_SIZE 4 // 32-bit tag (safe with per-packet rekey) +#define ASCON_OVERHEAD 8 // Total overhead: counter + tag + #define MAX_PACKET_PAYLOAD 184 #define MAX_PATH_SIZE 64 #define MAX_TRANS_UNIT 255 diff --git a/src/Packet.cpp b/src/Packet.cpp index 2d54ca4590..63e37ddb0d 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -38,17 +38,21 @@ uint8_t Packet::writeTo(uint8_t dest[]) const { return i; } -bool Packet::readFrom(const uint8_t src[], uint8_t len) { - uint8_t i = 0; +bool Packet::readFrom(const uint8_t src[], uint16_t len) { + if (len == 0) return false; // minimum packet needs at least header + uint16_t i = 0; header = src[i++]; if (hasTransportCodes()) { + if (i + 4 > len) return false; // need 4 bytes for transport codes memcpy(&transport_codes[0], &src[i], 2); i += 2; memcpy(&transport_codes[1], &src[i], 2); i += 2; } else { transport_codes[0] = transport_codes[1] = 0; } + if (i >= len) return false; // need at least path_len byte path_len = src[i++]; if (path_len > sizeof(path)) return false; // bad encoding + if (i + path_len > len) return false; // path extends beyond buffer memcpy(path, &src[i], path_len); i += path_len; if (i >= len) return false; // bad encoding payload_len = len - i; diff --git a/src/Packet.h b/src/Packet.h index 42d73f416c..dc4c417f03 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -31,10 +31,7 @@ namespace mesh { //... #define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc -#define PAYLOAD_VER_1 0x00 // 1-byte src/dest hashes, 2-byte MAC -#define PAYLOAD_VER_2 0x01 // FUTURE (eg. 2-byte hashes, 4-byte MAC ??) -#define PAYLOAD_VER_3 0x02 // FUTURE -#define PAYLOAD_VER_4 0x03 // FUTURE +#define PAYLOAD_VER_1 0x00 // 1-byte src/dest hashes; crypto determined by peer capability /** * \brief The fundamental transmission unit. @@ -98,7 +95,7 @@ class Packet { * \param src (IN) buffer containing blob * \param len the packet length (as returned by writeTo()) */ - bool readFrom(const uint8_t src[], uint8_t len); + bool readFrom(const uint8_t src[], uint16_t len); }; } diff --git a/src/Utils.cpp b/src/Utils.cpp index 186c8720a2..30f1dd377a 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -2,10 +2,26 @@ #include #include +// LibAscon - lightweight AEAD cipher (CC0 license) +// https://github.com/TheMatjaz/LibAscon +extern "C" { +#include +} + #ifdef ARDUINO #include #endif +#ifdef ESP32 + #include +#endif + +#ifdef NRF52_PLATFORM + #include + #include + #include +#endif + namespace mesh { uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) { @@ -142,7 +158,7 @@ int Utils::parseTextParts(char* text, const char* parts[], int max_num, char sep *sp++ = 0; // replace the seperator with a null, and skip past it } } - // if we hit the maximum parts, make sure LAST entry does NOT have separator + // if we hit the maximum parts, make sure LAST entry does NOT have separator while (*sp && *sp != separator) sp++; if (*sp) { *sp = 0; // replace the separator with null @@ -150,4 +166,241 @@ int Utils::parseTextParts(char* text, const char* parts[], int max_num, char sep return num; } -} \ No newline at end of file +// ========== Ascon Encryption: Ascon-128 with Per-Packet Key Derivation ========== +// +// Security design: +// 1. Per-packet key derivation: key = HMAC-SHA256(shared_secret, counter)[0:16] +// This enables short 4-byte tags (safe because key changes every packet) +// 2. Nonce: counter zero-padded to 16 bytes +// 3. Tag: 4 bytes (2^32 forgery attempts, but key changes before exhaustion) +// +// Packet format: [counter:4][ciphertext:N][tag:4] = 8 bytes overhead + +// Derive per-packet key from shared secret and counter +// key = HMAC-SHA256(shared_secret, counter)[0:16] +static void derivePacketKey(uint8_t* packet_key, const uint8_t* shared_secret, const uint8_t* counter) { + SHA256 sha; + uint8_t hmac_out[32]; + sha.resetHMAC(shared_secret, PUB_KEY_SIZE); + sha.update(counter, ASCON_COUNTER_SIZE); + sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac_out, 32); + memcpy(packet_key, hmac_out, ASCON_KEY_SIZE); +} + +// Expand 4-byte counter to 16-byte nonce (zero-padded) +static void expandNonce(uint8_t* nonce, const uint8_t* counter) { + memset(nonce, 0, ASCON_NONCE_SIZE); + memcpy(nonce + ASCON_NONCE_SIZE - ASCON_COUNTER_SIZE, counter, ASCON_COUNTER_SIZE); +} + +// Ascon Encrypt with Ascon-128 and per-packet key derivation +// Returns: total length (counter + ciphertext + tag) +int Utils::encryptAscon(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* plaintext, int plaintext_len) { + // Generate unique counter + uint8_t counter[ASCON_COUNTER_SIZE]; + generateCounter(counter); + + // Derive per-packet key + uint8_t packet_key[ASCON_KEY_SIZE]; + derivePacketKey(packet_key, shared_secret, counter); + + // Expand counter to full nonce + uint8_t nonce[ASCON_NONCE_SIZE]; + expandNonce(nonce, counter); + + // Write counter to output + memcpy(dest, counter, ASCON_COUNTER_SIZE); + + // Encrypt with Ascon-128 using LibAscon offline API + // Output: ciphertext directly after counter, tag after ciphertext + uint8_t* ciphertext_out = dest + ASCON_COUNTER_SIZE; + uint8_t* tag_out = dest + ASCON_COUNTER_SIZE + plaintext_len; + + ascon_aead128_encrypt( + ciphertext_out, // ciphertext output + tag_out, // tag output (truncated) + packet_key, // 16-byte key + nonce, // 16-byte nonce + NULL, // no associated data + plaintext, // plaintext input + 0, // assoc_data_len + (size_t)plaintext_len, // plaintext_len + ASCON_TAG_SIZE // tag_len (4 bytes - built-in truncation!) + ); + + // Clear sensitive material + memset(packet_key, 0, ASCON_KEY_SIZE); + + return ASCON_COUNTER_SIZE + plaintext_len + ASCON_TAG_SIZE; +} + +// Ascon Decrypt with Ascon-128 and per-packet key derivation +// Returns: plaintext length on success, 0 on authentication failure +int Utils::decryptAscon(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len) { + // Validate minimum length + if (src_len < ASCON_COUNTER_SIZE + ASCON_TAG_SIZE) { + return 0; + } + + int ciphertext_len = src_len - ASCON_COUNTER_SIZE - ASCON_TAG_SIZE; + + // Extract components + const uint8_t* counter = src; + const uint8_t* ciphertext = src + ASCON_COUNTER_SIZE; + const uint8_t* tag = src + ASCON_COUNTER_SIZE + ciphertext_len; + + // Derive per-packet key + uint8_t packet_key[ASCON_KEY_SIZE]; + derivePacketKey(packet_key, shared_secret, counter); + + // Expand counter to full nonce + uint8_t nonce[ASCON_NONCE_SIZE]; + expandNonce(nonce, counter); + + // Decrypt with Ascon-128 using LibAscon offline API + // LibAscon supports truncated tags directly via expected_tag_len! + bool valid = ascon_aead128_decrypt( + dest, // plaintext output + packet_key, // 16-byte key + nonce, // 16-byte nonce + NULL, // no associated data + ciphertext, // ciphertext input + tag, // expected tag (truncated) + 0, // assoc_data_len + (size_t)ciphertext_len, // ciphertext_len + ASCON_TAG_SIZE // expected_tag_len (4 bytes - built-in truncation!) + ); + + // Clear sensitive material + memset(packet_key, 0, ASCON_KEY_SIZE); + + if (!valid) { + memset(dest, 0, ciphertext_len); + return 0; + } + + return ciphertext_len; +} + +// Unified decryption: try Ascon first, fall back to legacy +int Utils::decryptAuto(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len, bool* was_ascon) { + // Try Ascon first - this is the happy path for updated clients + int len = decryptAscon(shared_secret, dest, src, src_len); + if (len > 0) { + if (was_ascon) *was_ascon = true; + return len; + } + + // Fall back to legacy (AES-ECB + HMAC) for old clients + len = MACThenDecrypt(shared_secret, dest, src, src_len); + if (len > 0) { + if (was_ascon) *was_ascon = false; + } + return len; +} + +// ========== Hardware RNG Implementation ========== + +void Utils::getHardwareRandom(uint8_t* dest, size_t size) { +#ifdef ESP32 + // ESP32: Use hardware TRNG (very fast, ~1μs) + for (size_t i = 0; i < size; i += 4) { + uint32_t rand_val = esp_random(); + size_t bytes_to_copy = (size - i) < 4 ? (size - i) : 4; + memcpy(&dest[i], &rand_val, bytes_to_copy); + } + +#elif defined(NRF52_PLATFORM) + // nRF52: Check if SoftDevice is enabled (BLE active) + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + + if (sd_enabled) { + // SoftDevice is active - must use its API to avoid hardfault + size_t remaining = size; + size_t offset = 0; + while (remaining > 0) { + uint8_t available = 0; + // Check how many random bytes are available + sd_rand_application_bytes_available_get(&available); + if (available > 0) { + uint8_t to_get = (remaining < available) ? remaining : available; + sd_rand_application_vector_get(dest + offset, to_get); + offset += to_get; + remaining -= to_get; + } + // If not enough bytes available, wait briefly for RNG to generate more + if (remaining > 0 && available == 0) { + delayMicroseconds(10); + } + } + } else { + // SoftDevice not active - safe to use RNG peripheral directly + NRF_RNG->TASKS_START = 1; + for (size_t i = 0; i < size; i++) { + while (!NRF_RNG->EVENTS_VALRDY); // Wait for random byte + dest[i] = NRF_RNG->VALUE; + NRF_RNG->EVENTS_VALRDY = 0; + } + NRF_RNG->TASKS_STOP = 1; // Stop RNG to save power + } + +#elif defined(RP2040_PLATFORM) + // RP2040: Use ROSC-based hardware random (fast, ~1μs) + for (size_t i = 0; i < size; i += 4) { + uint32_t rand_val = rp2040.hwrand32(); + size_t bytes_to_copy = (size - i) < 4 ? (size - i) : 4; + memcpy(&dest[i], &rand_val, bytes_to_copy); + } + +#elif defined(STM32_PLATFORM) + // STM32WL: Use hardware RNG peripheral + // Most STM32 variants with LoRa (STM32WL) have hardware RNG + #if defined(HAL_RNG_MODULE_ENABLED) + extern RNG_HandleTypeDef hrng; + for (size_t i = 0; i < size; i += 4) { + uint32_t rand_val; + HAL_RNG_GenerateRandomNumber(&hrng, &rand_val); + size_t bytes_to_copy = (size - i) < 4 ? (size - i) : 4; + memcpy(&dest[i], &rand_val, bytes_to_copy); + } + #else + // No hardware RNG - fall back but mix with more entropy sources + // WARNING: This path should be avoided for v2 encryption + MESH_DEBUG_PRINTLN("WARNING: STM32 without HAL_RNG - using weak entropy!"); + static uint32_t stm32_entropy_pool = 0xDEADBEEF; + for (size_t i = 0; i < size; i += 4) { + // Mix multiple sources for better entropy + stm32_entropy_pool ^= micros(); + stm32_entropy_pool ^= (millis() << 16); + stm32_entropy_pool = (stm32_entropy_pool * 1103515245) + 12345; // LCG step + uint32_t rand_val = stm32_entropy_pool ^ (i * 0x5DEECE66D); + size_t bytes_to_copy = (size - i) < 4 ? (size - i) : 4; + memcpy(&dest[i], &rand_val, bytes_to_copy); + } + #endif + +#else + // No secure RNG available - FAIL COMPILATION for v2 encryption safety + #error "No hardware RNG available. Ascon-128 AEAD requires secure nonce generation. \ + Supported platforms: ESP32, nRF52, RP2040, STM32 with HAL_RNG." +#endif +} + +// ========== Counter Generation for Ascon ========== + +// Simple monotonic counter for Ascon nonce. +// Since we derive a fresh key per packet (key = HMAC-SHA256(shared_secret, counter)), +// the counter only needs to be unique per conversation, not unpredictable. +// Starting at 0 enables future rekey signaling based on counter threshold. +static uint32_t s_ascon_counter = 0; + +void Utils::generateCounter(uint8_t* counter) { + memcpy(counter, &s_ascon_counter, ASCON_COUNTER_SIZE); + s_ascon_counter++; +} + +} diff --git a/src/Utils.h b/src/Utils.h index 5736b8747a..eca75347ad 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -54,6 +54,63 @@ class Utils { */ static int MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len); + /** + * \brief Ascon-128 AEAD encryption with per-packet key derivation. + * Layout: [counter (4 bytes)] [ciphertext] [tag (4 bytes)] + * Per-packet key = HMAC-SHA256(shared_secret, counter)[0:16] + * + * \param shared_secret 32-byte shared secret from key exchange + * \param dest destination buffer for encrypted output + * \param plaintext data to encrypt + * \param plaintext_len length of plaintext + * \returns total length of encrypted output (counter + ciphertext + tag) + */ + static int encryptAscon(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* plaintext, int plaintext_len); + + /** + * \brief Ascon-128 AEAD decryption with per-packet key derivation. + * Expects layout: [counter (4 bytes)] [ciphertext] [tag (4 bytes)] + * Per-packet key = HMAC-SHA256(shared_secret, counter)[0:16] + * + * \param shared_secret 32-byte shared secret from key exchange + * \param dest destination buffer for decrypted plaintext + * \param src encrypted data (counter + ciphertext + tag) + * \param src_len length of encrypted data + * \returns plaintext length on success, 0 on authentication failure + */ + static int decryptAscon(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len); + + /** + * \brief Unified decryption: tries Ascon first, falls back to legacy AES-ECB+HMAC. + * Use this for receiving packets from unknown sender crypto version. + * + * \param shared_secret 32-byte shared secret from key exchange + * \param dest destination buffer for decrypted plaintext + * \param src encrypted data + * \param src_len length of encrypted data + * \param was_ascon optional output: set to true if Ascon decryption succeeded, false if legacy + * \returns plaintext length on success, 0 on authentication failure + */ + static int decryptAuto(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len, bool* was_ascon = nullptr); + + /** + * \brief Get hardware random bytes from platform-specific TRNG. + * \param dest destination buffer + * \param size number of random bytes to generate + */ + static void getHardwareRandom(uint8_t* dest, size_t size); + + /** + * \brief Generate a 4-byte counter for Ascon nonce. + * Counter starts at 0 and increments monotonically. + * Safe because per-packet key derivation ensures unique keys. + * \param counter destination buffer (must be ASCON_COUNTER_SIZE bytes) + */ + static void generateCounter(uint8_t* counter); + /** * \brief converts 'src' bytes with given length to Hex representation, and null terminates. */ diff --git a/src/helpers/AdvertDataHelpers.h b/src/helpers/AdvertDataHelpers.h index abe14cbd00..cc9ea38297 100644 --- a/src/helpers/AdvertDataHelpers.h +++ b/src/helpers/AdvertDataHelpers.h @@ -16,6 +16,9 @@ #define ADV_FEAT2_MASK 0x40 // FUTURE #define ADV_NAME_MASK 0x80 +// FEAT1 bit flags (encoded when ADV_FEAT1_MASK is set) +#define ADV_FEAT1_ASCON_CAPABLE 0x0001 + class AdvertDataBuilder { uint8_t _type; bool _has_loc; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index aebfc1b644..5f3313a24d 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -21,6 +21,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) { uint8_t app_data_len; { AdvertDataBuilder builder(ADV_TYPE_CHAT, name); + builder.setFeat1(ADV_FEAT1_ASCON_CAPABLE); // Advertise Ascon encryption support app_data_len = builder.encodeTo(app_data); } @@ -32,6 +33,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl uint8_t app_data_len; { AdvertDataBuilder builder(ADV_TYPE_CHAT, name, lat, lon); + builder.setFeat1(ADV_FEAT1_ASCON_CAPABLE); // Advertise Ascon encryption support app_data_len = builder.encodeTo(app_data); } @@ -165,6 +167,8 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } from->last_advert_timestamp = timestamp; from->lastmod = getRTCClock()->getCurrentTime(); + // Track Ascon encryption capability from advertisement FEAT1 flags + from->supports_ascon = (parser.getFeat1() & ADV_FEAT1_ASCON_CAPABLE) != 0; onDiscoveredContact(*from, is_new, packet->path_len, packet->path); // let UI know } @@ -188,6 +192,18 @@ void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } } +void BaseChatMesh::onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) { + int i = matching_peer_indexes[peer_idx]; + if (i >= 0 && i < num_contacts) { + // Auto-upgrade: if peer sent Ascon packet, mark them as Ascon-capable + // Note: We only upgrade (false->true), never downgrade, to be safe + if (supports_ascon && !contacts[i].supports_ascon) { + contacts[i].supports_ascon = true; + MESH_DEBUG_PRINTLN("Auto-detected Ascon capability for contact %d", i); + } + } +} + void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= num_contacts) { @@ -215,7 +231,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); + PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4, from.supports_ascon); if (path) sendFloodScoped(from, path, TXT_ACK_DELAY); } else { sendAckTo(from, ack_hash); @@ -226,7 +242,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra) - mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0); + mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0, from.supports_ascon); if (path) sendFloodScoped(from, path); } } else if (flags == TXT_TYPE_SIGNED_PLAIN) { @@ -242,7 +258,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); + PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4, from.supports_ascon); if (path) sendFloodScoped(from, path, TXT_ACK_DELAY); } else { sendAckTo(from, ack_hash); @@ -258,10 +274,10 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len); + PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len, from.supports_ascon); if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len, from.supports_ascon); if (reply) { if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY); @@ -327,7 +343,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) // override this method in various firmwares, if there's a better strategy - mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0); + mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0, contact.supports_ascon); if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay } @@ -344,8 +360,9 @@ int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel d #endif void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) { - uint8_t txt_type = data[4]; - if (type == PAYLOAD_TYPE_GRP_TXT && len > 5 && (txt_type >> 2) == 0) { // 0 = plain text msg + if (type == PAYLOAD_TYPE_GRP_TXT && len > 5) { + uint8_t txt_type = data[4]; + if ((txt_type >> 2) == 0) { // 0 = plain text msg uint32_t timestamp; memcpy(×tamp, data, 4); @@ -354,6 +371,7 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes // notify UI of this new message onChannelMessageRecv(channel, packet, timestamp, (const char *) &data[5]); // let UI know + } } } @@ -376,7 +394,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } - return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len); + return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len, recipient.supports_ascon); } int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) { @@ -407,7 +425,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); memcpy(&temp[5], text, text_len + 1); - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len); + auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len, recipient.supports_ascon); if (pkt == NULL) return MSG_SEND_FAILED; uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -429,15 +447,22 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = 0; // TXT_TYPE_PLAIN - sprintf((char *) &temp[5], "%s: ", sender_name); // : - char *ep = strchr((char *) &temp[5], 0); - int prefix_len = ep - (char *) &temp[5]; + int n = snprintf((char *) &temp[5], sizeof(temp) - 5, "%s: ", sender_name); // : + if (n < 0) return false; + int prefix_len = strlen((char *) &temp[5]); + if (prefix_len > MAX_TEXT_LEN) { + prefix_len = MAX_TEXT_LEN; + temp[5 + prefix_len] = 0; + } + char *ep = (char *) &temp[5 + prefix_len]; if (text_len + prefix_len > MAX_TEXT_LEN) text_len = MAX_TEXT_LEN - prefix_len; memcpy(ep, text, text_len); ep[text_len] = 0; // null terminator - auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len); + // Use Ascon if channel has the flag set, otherwise use legacy for backwards compatibility + bool use_ascon = (channel.flags & CHANNEL_FLAG_ASCON) != 0; + auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len, use_ascon); if (pkt) { sendFloodScoped(channel, pkt); return true; @@ -496,7 +521,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } - pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen); + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen, recipient.supports_ascon); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -517,11 +542,12 @@ int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, mesh::Packet* pkt; { uint8_t temp[MAX_PACKET_PAYLOAD]; + if (len > MAX_PACKET_PAYLOAD - 4) return MSG_SEND_FAILED; tag = getRTCClock()->getCurrentTimeUnique(); memcpy(temp, &tag, 4); // tag to match later (also extra blob to help make packet_hash unique) memcpy(&temp[4], data, len); - pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len); + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len, recipient.supports_ascon); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -548,7 +574,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len, recipient.supports_ascon); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -575,7 +601,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp)); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp), recipient.supports_ascon); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -698,7 +724,7 @@ void BaseChatMesh::checkConnections() { // calc expected ACK reply mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); - auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9); + auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9, contact->supports_ascon); if (pkt) { sendDirect(pkt, contact->out_path, contact->out_path_len); } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index fd391b9808..c894f97c53 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -125,6 +125,7 @@ class BaseChatMesh : public mesh::Mesh { void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; + void onPeerAsconCapabilityDetected(int peer_idx, bool supports_ascon) override; void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; 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; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index dfbc3fce1f..ef1c7ed543 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -18,6 +18,7 @@ struct ClientInfo { uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t last_timestamp; // by THEIR clock (transient) uint32_t last_activity; // by OUR clock (transient) + bool supports_ascon; // Peer advertised Ascon encryption capability in FEAT1 (transient) union { struct { uint32_t sync_since; // sync messages SINCE this timestamp (by OUR clock) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 10ab866912..c5fc50d6af 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -183,12 +183,15 @@ void CommonCLI::savePrefs() { uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) { AdvertDataBuilder builder(node_type, _prefs->node_name); + builder.setFeat1(ADV_FEAT1_ASCON_CAPABLE); // Advertise Ascon encryption support return builder.encodeTo(app_data); } else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) { AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon); + builder.setFeat1(ADV_FEAT1_ASCON_CAPABLE); // Advertise Ascon encryption support return builder.encodeTo(app_data); } else { AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon); + builder.setFeat1(ADV_FEAT1_ASCON_CAPABLE); // Advertise Ascon encryption support return builder.encodeTo(app_data); } } diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index eff07741ab..6795814931 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -10,6 +10,7 @@ struct ContactInfo { uint8_t flags; int8_t out_path_len; mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated + bool supports_ascon; // Peer advertised Ascon encryption capability in FEAT1 uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock uint32_t lastmod; // by OUR clock