diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 60a5a75fec..055b032d0f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -874,10 +874,10 @@ void MyMesh::begin(bool has_display) { BaseChatMesh::begin(); if (!_store->loadMainIdentity(self_id)) { - self_id = radio_new_identity(); // create new random identity + self_id = mesh::LocalIdentity(getRNG()); // create new random identity int count = 0; while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes - self_id = radio_new_identity(); + self_id = mesh::LocalIdentity(getRNG()); count++; } _store->saveMainIdentity(self_id); @@ -911,8 +911,7 @@ void MyMesh::begin(bool has_display) { if (_prefs.ble_pin == 0) { #ifdef DISPLAY_CLASS if (has_display && BLE_PIN_CODE == 123456) { - StdRNG rng; - _active_ble_pin = rng.nextInt(100000, 999999); // random pin each session + _active_ble_pin = getRNG()->nextInt(100000, 999999); // random pin each session } else { _active_ble_pin = BLE_PIN_CODE; // otherwise static pin } diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 876dc9c33c..755cfa8c30 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -105,10 +105,12 @@ void halt() { while (1) ; } +#ifndef PIO_UNIT_TESTING void setup() { Serial.begin(115200); board.begin(); + mesh::initHardwareRNG(); #ifdef DISPLAY_CLASS DisplayDriver* disp = NULL; @@ -123,12 +125,13 @@ void setup() { } #endif - if (!radio_init()) { halt(); } - - fast_rng.begin(radio_get_rng_seed()); - #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); + fast_rng.attachPersistence(InternalFS, "/seed.rng"); + fast_rng.setRadioEntropySource(radio_driver); + fast_rng.begin(); + mesh::deinitHardwareRNG(); + if (!radio_init()) { halt(); } #if defined(QSPIFLASH) if (!QSPIFlash.begin()) { // debug output might not be available at this point, might be too early. maybe should fall back to InternalFS here? @@ -158,6 +161,11 @@ void setup() { the_mesh.startInterface(serial_interface); #elif defined(RP2040_PLATFORM) LittleFS.begin(); + fast_rng.attachPersistence(LittleFS, "/seed.rng"); + fast_rng.setRadioEntropySource(radio_driver); + fast_rng.begin(); + mesh::deinitHardwareRNG(); + if (!radio_init()) { halt(); } store.begin(); the_mesh.begin( #ifdef DISPLAY_CLASS @@ -184,6 +192,11 @@ void setup() { the_mesh.startInterface(serial_interface); #elif defined(ESP32) SPIFFS.begin(true); + fast_rng.attachPersistence(SPIFFS, "/seed.rng"); + fast_rng.setRadioEntropySource(radio_driver); + fast_rng.begin(); + mesh::deinitHardwareRNG(); + if (!radio_init()) { halt(); } store.begin(); the_mesh.begin( #ifdef DISPLAY_CLASS @@ -230,3 +243,4 @@ void loop() { #endif rtc_clock.tick(); } +#endif diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 3507959297..2830b7f5d8 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -29,24 +29,29 @@ void halt() { } void loadOrCreateIdentity() { + FILESYSTEM* fs; #if defined(NRF52_PLATFORM) InternalFS.begin(); + fs = &InternalFS; IdentityStore store(InternalFS, ""); #elif defined(ESP32) SPIFFS.begin(true); + fs = &SPIFFS; IdentityStore store(SPIFFS, "/identity"); #elif defined(RP2040_PLATFORM) LittleFS.begin(); + fs = &LittleFS; IdentityStore store(LittleFS, "/identity"); store.begin(); #else #error "Filesystem not defined" #endif + rng.attachPersistence(*fs, "/seed.rng"); if (!store.load("_main", identity)) { - identity = radio_new_identity(); + identity = mesh::LocalIdentity(&rng); while (identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF) { - identity = radio_new_identity(); + identity = mesh::LocalIdentity(&rng); } store.save("_main", identity); } @@ -70,8 +75,15 @@ void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { *errors = radio_driver.getPacketsRecvErrors(); } +#ifndef PIO_UNIT_TESTING void setup() { board.begin(); + mesh::initHardwareRNG(); + + loadOrCreateIdentity(); + rng.setRadioEntropySource(radio_driver); + rng.begin(); + mesh::deinitHardwareRNG(); if (!radio_init()) { halt(); @@ -79,9 +91,6 @@ void setup() { radio_driver.begin(); - rng.begin(radio_get_rng_seed()); - loadOrCreateIdentity(); - sensors.begin(); #if defined(KISS_UART_RX) && defined(KISS_UART_TX) @@ -144,3 +153,4 @@ void loop() { } radio_driver.loop(); } +#endif diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index e37078ce5f..8bdce9b602 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -28,11 +28,13 @@ static unsigned long userBtnDownAt = 0; #define USER_BTN_HOLD_OFF_MILLIS 1500 #endif +#ifndef PIO_UNIT_TESTING void setup() { Serial.begin(115200); delay(1000); board.begin(); + mesh::initHardwareRNG(); #if defined(MESH_DEBUG) && defined(NRF52_PLATFORM) // give some extra time for serial to settle so @@ -52,13 +54,6 @@ void setup() { } #endif - if (!radio_init()) { - MESH_DEBUG_PRINTLN("Radio init failed!"); - halt(); - } - - fast_rng.begin(radio_get_rng_seed()); - FILESYSTEM* fs; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); @@ -76,12 +71,22 @@ void setup() { #else #error "need to define filesystem" #endif + fast_rng.attachPersistence(*fs, "/seed.rng"); + fast_rng.setRadioEntropySource(radio_driver); + fast_rng.begin(); + mesh::deinitHardwareRNG(); + + if (!radio_init()) { + MESH_DEBUG_PRINTLN("Radio init failed!"); + halt(); + } + if (!store.load("_main", the_mesh.self_id)) { MESH_DEBUG_PRINTLN("Generating new keypair"); - the_mesh.self_id = radio_new_identity(); // create new random identity + the_mesh.self_id = mesh::LocalIdentity(the_mesh.getRNG()); // create new random identity int count = 0; while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes - the_mesh.self_id = radio_new_identity(); count++; + the_mesh.self_id = mesh::LocalIdentity(the_mesh.getRNG()); count++; } store.save("_main", the_mesh.self_id); } @@ -168,3 +173,4 @@ void loop() { #endif } } +#endif diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 825fb007d5..2c9f633ad5 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -18,11 +18,13 @@ void halt() { static char command[MAX_POST_TEXT_LEN+1]; +#ifndef PIO_UNIT_TESTING void setup() { Serial.begin(115200); delay(1000); board.begin(); + mesh::initHardwareRNG(); #ifdef DISPLAY_CLASS if (display.begin()) { @@ -33,10 +35,6 @@ void setup() { } #endif - if (!radio_init()) { halt(); } - - fast_rng.begin(radio_get_rng_seed()); - FILESYSTEM* fs; #if defined(NRF52_PLATFORM) InternalFS.begin(); @@ -54,11 +52,18 @@ void setup() { #else #error "need to define filesystem" #endif + fast_rng.attachPersistence(*fs, "/seed.rng"); + fast_rng.setRadioEntropySource(radio_driver); + fast_rng.begin(); + mesh::deinitHardwareRNG(); + + if (!radio_init()) { halt(); } + if (!store.load("_main", the_mesh.self_id)) { - the_mesh.self_id = radio_new_identity(); // create new random identity + the_mesh.self_id = mesh::LocalIdentity(the_mesh.getRNG()); // create new random identity int count = 0; while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes - the_mesh.self_id = radio_new_identity(); count++; + the_mesh.self_id = mesh::LocalIdentity(the_mesh.getRNG()); count++; } store.save("_main", the_mesh.self_id); } @@ -114,3 +119,4 @@ void loop() { #endif rtc_clock.tick(); } +#endif diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index ab14d3933f..8b27d06064 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -307,14 +307,6 @@ class MyMesh : public BaseChatMesh, ContactVisitor { IdentityStore store(fs, "/identity"); #endif if (!store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name))) { // legacy: node_name was from identity file - // Need way to get some entropy to seed RNG - Serial.println("Press ENTER to generate key:"); - char c = 0; - while (c != '\n') { // wait for ENTER to be pressed - if (Serial.available()) c = Serial.read(); - } - ((StdRNG *)getRNG())->begin(millis()); - self_id = mesh::LocalIdentity(getRNG()); // create new random identity int count = 0; while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes @@ -555,27 +547,37 @@ void halt() { while (1) ; } +#ifndef PIO_UNIT_TESTING void setup() { Serial.begin(115200); board.begin(); + mesh::initHardwareRNG(); - if (!radio_init()) { halt(); } - - fast_rng.begin(radio_get_rng_seed()); + FILESYSTEM* fs; #if defined(NRF52_PLATFORM) InternalFS.begin(); - the_mesh.begin(InternalFS); + fs = &InternalFS; + fast_rng.attachPersistence(InternalFS, "/seed.rng"); #elif defined(RP2040_PLATFORM) LittleFS.begin(); - the_mesh.begin(LittleFS); + fs = &LittleFS; + fast_rng.attachPersistence(LittleFS, "/seed.rng"); #elif defined(ESP32) SPIFFS.begin(true); - the_mesh.begin(SPIFFS); + fs = &SPIFFS; + fast_rng.attachPersistence(SPIFFS, "/seed.rng"); #else #error "need to define filesystem" #endif + fast_rng.setRadioEntropySource(radio_driver); + fast_rng.begin(); + mesh::deinitHardwareRNG(); + + if (!radio_init()) { halt(); } + + the_mesh.begin(*fs); radio_set_params(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR); radio_set_tx_power(the_mesh.getTxPowerPref()); @@ -592,3 +594,4 @@ void loop() { the_mesh.loop(); rtc_clock.tick(); } +#endif diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index 330adcc2e4..50bd73bd77 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -52,11 +52,13 @@ void halt() { static char command[160]; +#ifndef PIO_UNIT_TESTING void setup() { Serial.begin(115200); delay(1000); board.begin(); + mesh::initHardwareRNG(); #ifdef DISPLAY_CLASS if (display.begin()) { @@ -66,10 +68,6 @@ void setup() { } #endif - if (!radio_init()) { halt(); } - - fast_rng.begin(radio_get_rng_seed()); - FILESYSTEM* fs; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); @@ -87,12 +85,19 @@ void setup() { #else #error "need to define filesystem" #endif + fast_rng.attachPersistence(*fs, "/seed.rng"); + fast_rng.setRadioEntropySource(radio_driver); + fast_rng.begin(); + mesh::deinitHardwareRNG(); + + if (!radio_init()) { halt(); } + if (!store.load("_main", the_mesh.self_id)) { MESH_DEBUG_PRINTLN("Generating new keypair"); - the_mesh.self_id = radio_new_identity(); // create new random identity + the_mesh.self_id = mesh::LocalIdentity(the_mesh.getRNG()); // create new random identity int count = 0; while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes - the_mesh.self_id = radio_new_identity(); count++; + the_mesh.self_id = mesh::LocalIdentity(the_mesh.getRNG()); count++; } store.save("_main", the_mesh.self_id); } @@ -148,3 +153,4 @@ void loop() { #endif rtc_clock.tick(); } +#endif diff --git a/lib/ascon/api.h b/lib/ascon/api.h new file mode 100644 index 0000000000..6713345768 --- /dev/null +++ b/lib/ascon/api.h @@ -0,0 +1,5 @@ +#define CRYPTO_VERSION "1.3.0" +#define CRYPTO_BYTES 64 +#define ASCON_HASH_BYTES 0 /* XOF */ +#define ASCON_HASH_ROUNDS 12 +#define ASCON_VARIANT 3 diff --git a/lib/ascon/ascon.h b/lib/ascon/ascon.h new file mode 100644 index 0000000000..ad7f1eff16 --- /dev/null +++ b/lib/ascon/ascon.h @@ -0,0 +1,70 @@ +#ifndef ASCON_H_ +#define ASCON_H_ + +#include + +#include "api.h" +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef union { + uint64_t x[5]; + uint32_t w[5][2]; + uint8_t b[5][8]; +} ascon_state_t; + +#ifdef ASCON_AEAD_RATE + +#define ASCON_KEYWORDS (CRYPTO_KEYBYTES + 7) / 8 + +typedef union { + uint64_t x[ASCON_KEYWORDS]; + uint32_t w[ASCON_KEYWORDS][2]; + uint8_t b[ASCON_KEYWORDS][8]; +} ascon_key_t; + +#if !ASCON_INLINE_MODE + +void ascon_loadkey(ascon_key_t* key, const uint8_t* k); +void ascon_initaead(ascon_state_t* s, const ascon_key_t* key, + const uint8_t* npub); +void ascon_adata(ascon_state_t* s, const uint8_t* ad, uint64_t adlen); +void ascon_encrypt(ascon_state_t* s, uint8_t* c, const uint8_t* m, + uint64_t mlen); +void ascon_decrypt(ascon_state_t* s, uint8_t* m, const uint8_t* c, + uint64_t clen); +void ascon_final(ascon_state_t* s, const ascon_key_t* k); + +#endif + +int ascon_aead_encrypt(uint8_t* t, uint8_t* c, const uint8_t* m, uint64_t mlen, + const uint8_t* ad, uint64_t adlen, const uint8_t* npub, + const uint8_t* k); +int ascon_aead_decrypt(uint8_t* m, const uint8_t* t, const uint8_t* c, + uint64_t clen, const uint8_t* ad, uint64_t adlen, + const uint8_t* npub, const uint8_t* k); + +#endif + +#ifdef ASCON_HASH_BYTES + +#if !ASCON_INLINE_MODE + +void ascon_inithash(ascon_state_t* s); +void ascon_absorb(ascon_state_t* s, const uint8_t* in, uint64_t inlen); +void ascon_squeeze(ascon_state_t* s, uint8_t* out, uint64_t outlen); + +#endif + +int ascon_xof(uint8_t* out, uint64_t outlen, const uint8_t* in, uint64_t inlen); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* ASCON_H_ */ diff --git a/lib/ascon/config.h b/lib/ascon/config.h new file mode 100644 index 0000000000..99404fe3f4 --- /dev/null +++ b/lib/ascon/config.h @@ -0,0 +1,21 @@ +#ifndef CONFIG_H_ +#define CONFIG_H_ + +/* inline the ascon mode */ +#ifndef ASCON_INLINE_MODE +#define ASCON_INLINE_MODE 0 +#endif + +/* inline all permutations */ +#ifndef ASCON_INLINE_PERM +#define ASCON_INLINE_PERM 1 +#endif + +/* unroll permutation loops */ +#ifndef ASCON_UNROLL_LOOPS +#define ASCON_UNROLL_LOOPS 1 +#endif + +#define ASCON_HASH_BYTES 1 + +#endif /* CONFIG_H_ */ diff --git a/lib/ascon/constants.h b/lib/ascon/constants.h new file mode 100644 index 0000000000..0fe903aade --- /dev/null +++ b/lib/ascon/constants.h @@ -0,0 +1,93 @@ +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +#include + +#define ASCON_80PQ_VARIANT 0 +#define ASCON_AEAD_VARIANT 1 +#define ASCON_HASH_VARIANT 2 +#define ASCON_XOF_VARIANT 3 +#define ASCON_CXOF_VARIANT 4 +#define ASCON_MAC_VARIANT 5 +#define ASCON_PRF_VARIANT 6 +#define ASCON_PRFS_VARIANT 7 + +#define ASCON_TAG_SIZE 16 +#define ASCON_HASH_SIZE 32 + +#define ASCON_128_RATE 8 +#define ASCON_128A_RATE 16 +#define ASCON_HASH_RATE 8 +#define ASCON_PRF_IN_RATE 32 +#define ASCON_PRFA_IN_RATE 40 +#define ASCON_PRF_OUT_RATE 16 + +#define ASCON_PA_ROUNDS 12 +#define ASCON_128_PB_ROUNDS 6 +#define ASCON_128A_PB_ROUNDS 8 +#define ASCON_HASH_PB_ROUNDS 12 +#define ASCON_HASHA_PB_ROUNDS 8 +#define ASCON_PRF_PB_ROUNDS 12 +#define ASCON_PRFA_PB_ROUNDS 8 + +#define ASCON_128_IV 0x00000800806c0001ull +#define ASCON_128A_IV 0x00001000808c0001ull +#define ASCON_80PQ_IV 0x00000000806c0800ull + +#define ASCON_HASH_IV 0x0000080100cc0002ull +#define ASCON_HASHA_IV 0x00000801008c0002ull +#define ASCON_XOF_IV 0x0000080000cc0003ull +#define ASCON_XOFA_IV 0x00000800008c0003ull +#define ASCON_CXOF_IV 0x0000080000cc0004ull +#define ASCON_CXOFA_IV 0x00000800008c0004ull + +#define ASCON_MAC_IV 0x0010200080cc0005ull +#define ASCON_MACA_IV 0x00102800808c0005ull +#define ASCON_PRF_IV 0x0010200000cc0006ull +#define ASCON_PRFA_IV 0x00102800008c0006ull +#define ASCON_PRFS_IV 0x00000000800c0007ull + +#define ASCON_HASH_IV0 0x9b1e5494e934d681ull +#define ASCON_HASH_IV1 0x4bc3a01e333751d2ull +#define ASCON_HASH_IV2 0xae65396c6b34b81aull +#define ASCON_HASH_IV3 0x3c7fd4a4d56a4db3ull +#define ASCON_HASH_IV4 0x1a5c464906c5976dull + +#define ASCON_HASHA_IV0 0xe2ffb4d17ffcadc5ull +#define ASCON_HASHA_IV1 0xdd364b655fa88cebull +#define ASCON_HASHA_IV2 0xdcaabe85a70319d2ull +#define ASCON_HASHA_IV3 0xd98f049404be3214ull +#define ASCON_HASHA_IV4 0xca8c9d516e8a2221ull + +#define ASCON_XOF_IV0 0xda82ce768d9447ebull +#define ASCON_XOF_IV1 0xcc7ce6c75f1ef969ull +#define ASCON_XOF_IV2 0xe7508fd780085631ull +#define ASCON_XOF_IV3 0x0ee0ea53416b58ccull +#define ASCON_XOF_IV4 0xe0547524db6f0bdeull + +#define ASCON_XOFA_IV0 0xf3040e5017d92943ull +#define ASCON_XOFA_IV1 0xc474f6e3ae01892eull +#define ASCON_XOFA_IV2 0xbf5cb3ca954805e0ull +#define ASCON_XOFA_IV3 0xd9c28702ccf962efull +#define ASCON_XOFA_IV4 0x5923fa01f4b0e72full + +#define RC0 0xf0 +#define RC1 0xe1 +#define RC2 0xd2 +#define RC3 0xc3 +#define RC4 0xb4 +#define RC5 0xa5 +#define RC6 0x96 +#define RC7 0x87 +#define RC8 0x78 +#define RC9 0x69 +#define RCa 0x5a +#define RCb 0x4b + +#define RC(i) (i) + +#define START(n) ((3 + (n)) << 4 | (12 - (n))) +#define INC -0x0f +#define END 0x3c + +#endif /* CONSTANTS_H_ */ diff --git a/lib/ascon/crypto_hash.h b/lib/ascon/crypto_hash.h new file mode 100644 index 0000000000..d3bf787179 --- /dev/null +++ b/lib/ascon/crypto_hash.h @@ -0,0 +1,2 @@ +int crypto_hash(unsigned char *out, const unsigned char *in, + unsigned long long inlen); \ No newline at end of file diff --git a/lib/ascon/forceinline.h b/lib/ascon/forceinline.h new file mode 100644 index 0000000000..e66c1eb093 --- /dev/null +++ b/lib/ascon/forceinline.h @@ -0,0 +1,23 @@ +#ifndef FORCEINLINE_H_ +#define FORCEINLINE_H_ + +/* define forceinline macro */ +#ifdef _MSC_VER +#define forceinline __forceinline +#elif defined(__GNUC__) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define forceinline inline __attribute__((__always_inline__)) +#else +#define forceinline static inline +#endif +#elif defined(__CLANG__) +#if __has_attribute(__always_inline__) +#define forceinline inline __attribute__((__always_inline__)) +#else +#define forceinline inline +#endif +#else +#define forceinline inline +#endif + +#endif /* FORCEINLINE_H_ */ diff --git a/lib/ascon/goal-constbranch b/lib/ascon/goal-constbranch new file mode 100644 index 0000000000..1a9c048242 --- /dev/null +++ b/lib/ascon/goal-constbranch @@ -0,0 +1 @@ +Branches reviewed 2020-11-13 by Martin Schläffer. diff --git a/lib/ascon/goal-constindex b/lib/ascon/goal-constindex new file mode 100644 index 0000000000..316d11da45 --- /dev/null +++ b/lib/ascon/goal-constindex @@ -0,0 +1 @@ +Addresses reviewed 2020-11-13 by Martin Schläffer. diff --git a/lib/ascon/hash.c b/lib/ascon/hash.c new file mode 100644 index 0000000000..6ddc3f6949 --- /dev/null +++ b/lib/ascon/hash.c @@ -0,0 +1,84 @@ +#include "api.h" +#include "ascon.h" +#include "crypto_hash.h" +#include "permutations.h" +#include "printstate.h" + +#if !ASCON_INLINE_MODE +#undef forceinline +#define forceinline +#endif + +#ifdef ASCON_HASH_BYTES + +#if ASCON_HASH_BYTES == 32 && ASCON_HASH_ROUNDS == 12 +#define IV(i) ASCON_HASH_IV##i +#elif ASCON_HASH_BYTES == 32 && ASCON_HASH_ROUNDS == 8 +#define IV(i) ASCON_HASHA_IV##i +#elif ASCON_HASH_BYTES == 0 && ASCON_HASH_ROUNDS == 12 +#define IV(i) ASCON_XOF_IV##i +#elif ASCON_HASH_BYTES == 0 && ASCON_HASH_ROUNDS == 8 +#define IV(i) ASCON_XOFA_IV##i +#endif + +forceinline void ascon_inithash(ascon_state_t* s) { + /* initialize */ +#ifdef ASCON_PRINT_STATE + *s = (ascon_state_t){{IV(), 0, 0, 0, 0}}; + printstate("initial value", s); + P(s, 12); +#else + *s = (ascon_state_t){{IV(0), IV(1), IV(2), IV(3), IV(4)}}; +#endif + printstate("initialization", s); +} + +forceinline void ascon_absorb(ascon_state_t* s, const uint8_t* in, + uint64_t inlen) { + /* absorb full plaintext blocks */ + while (inlen >= ASCON_HASH_RATE) { + s->x[0] ^= LOAD(in, 8); + printstate("absorb plaintext", s); + P(s, ASCON_HASH_ROUNDS); + in += ASCON_HASH_RATE; + inlen -= ASCON_HASH_RATE; + } + /* absorb final plaintext block */ + s->x[0] ^= LOADBYTES(in, inlen); + s->x[0] ^= PAD(inlen); + printstate("pad plaintext", s); +} + +forceinline void ascon_squeeze(ascon_state_t* s, uint8_t* out, + uint64_t outlen) { + /* squeeze full output blocks */ + P(s, 12); + while (outlen > ASCON_HASH_RATE) { + STORE(out, s->x[0], 8); + printstate("squeeze output", s); + P(s, ASCON_HASH_ROUNDS); + out += ASCON_HASH_RATE; + outlen -= ASCON_HASH_RATE; + } + /* squeeze final output block */ + STOREBYTES(out, s->x[0], outlen); + printstate("squeeze output", s); +} + +int ascon_xof(uint8_t* out, uint64_t outlen, const uint8_t* in, + uint64_t inlen) { + ascon_state_t s; + printbytes("m", in, inlen); + ascon_inithash(&s); + ascon_absorb(&s, in, inlen); + ascon_squeeze(&s, out, outlen); + printbytes("h", out, outlen); + return 0; +} + +int crypto_hash(unsigned char* out, const unsigned char* in, + unsigned long long inlen) { + return ascon_xof(out, CRYPTO_BYTES, in, inlen); +} + +#endif diff --git a/lib/ascon/implementors b/lib/ascon/implementors new file mode 100644 index 0000000000..b110c1a685 --- /dev/null +++ b/lib/ascon/implementors @@ -0,0 +1,2 @@ +Christoph Dobraunig +Martin Schläffer diff --git a/lib/ascon/lendian.h b/lib/ascon/lendian.h new file mode 100644 index 0000000000..0fdb326499 --- /dev/null +++ b/lib/ascon/lendian.h @@ -0,0 +1,39 @@ +#ifndef ENDIAN_H_ +#define ENDIAN_H_ + +#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + defined(_MSC_VER) + +/* macros for little endian machines */ +#ifdef PRAGMA_ENDIAN +#pragma message("Use macros for little endian machines") +#endif +#define U64LE(x) (x) +#define U32LE(x) (x) +#define U16LE(x) (x) + +#elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + +/* macros for big endian machines */ +#ifdef PRAGMA_ENDIAN +#pragma message("Use macros for big endian machines") +#endif +#define U64LE(x) \ + (((0x00000000000000FFULL & (x)) << 56) | \ + ((0x000000000000FF00ULL & (x)) << 40) | \ + ((0x0000000000FF0000ULL & (x)) << 24) | \ + ((0x00000000FF000000ULL & (x)) << 8) | \ + ((0x000000FF00000000ULL & (x)) >> 8) | \ + ((0x0000FF0000000000ULL & (x)) >> 24) | \ + ((0x00FF000000000000ULL & (x)) >> 40) | \ + ((0xFF00000000000000ULL & (x)) >> 56)) +#define U32LE(x) \ + (((0x000000FF & (x)) << 24) | ((0x0000FF00 & (x)) << 8) | \ + ((0x00FF0000 & (x)) >> 8) | ((0xFF000000 & (x)) >> 24)) +#define U16LE(x) (((0x00FF & (x)) << 8) | ((0xFF00 & (x)) >> 8)) + +#else +#error "Ascon byte order macros not defined in lendian.h" +#endif + +#endif /* ENDIAN_H_ */ diff --git a/lib/ascon/library.json b/lib/ascon/library.json new file mode 100644 index 0000000000..f47cfc35b9 --- /dev/null +++ b/lib/ascon/library.json @@ -0,0 +1,51 @@ +{ + "name": "ascon", + "version": "1.3.0", + "description": "ASCon cryptographic library - NIST Lightweight Cryptography standard for authenticated encryption and hashing - This is asconxof128/opt32 without inlining", + "keywords": "crypto, encryption, hash, ascon, aes, lightweight", + "authors": [ + { + "name": "Jakob Dombrowski", + "maintainer": true + }, + { + "name": "Lorenzo Grassi", + "maintainer": true + }, + { + "name": "Yassine Lellahi", + "maintainer": true + }, + { + "name": "Yi Lu", + "maintainer": true + }, + { + "name": "Daniel Nilsson", + "maintainer": true + }, + { + "name": "Torete Pytkow", + "maintainer": true + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ascon-team/ascon-reference.git" + }, + "license": "CC0 1.0 Universal", + "homepage": "https://ascon.iaik.tugraz.at/", + "frameworks": ["*"], + "platforms": ["*"], + "build": { + "flags": [ + "-Ilib/ascon", + "-fomit-frame-pointer", + "-fno-strict-aliasing", + "-std=c99", + "-O3", + "-DASCON_INLINE_MODE=0", + "-DASCON_HASH_BYTES=1" + ] + } +} diff --git a/lib/ascon/permutations.c b/lib/ascon/permutations.c new file mode 100644 index 0000000000..02bbadb687 --- /dev/null +++ b/lib/ascon/permutations.c @@ -0,0 +1,29 @@ +#include "permutations.h" + +#if !ASCON_INLINE_PERM && ASCON_UNROLL_LOOPS + +void P12(ascon_state_t* s) { P12ROUNDS(s); } + +#endif + +#if ((defined(ASCON_AEAD_RATE) && ASCON_AEAD_RATE == 16) || \ + (defined(ASCON_HASH_ROUNDS) && ASCON_HASH_ROUNDS == 8) || \ + (defined(ASCON_PRF_ROUNDS) && ASCON_PRF_ROUNDS == 8)) && \ + !ASCON_INLINE_PERM && ASCON_UNROLL_LOOPS + +void P8(ascon_state_t* s) { P8ROUNDS(s); } + +#endif + +#if (defined(ASCON_AEAD_RATE) && ASCON_AEAD_RATE == 8) && \ + !ASCON_INLINE_PERM && ASCON_UNROLL_LOOPS + +void P6(ascon_state_t* s) { P6ROUNDS(s); } + +#endif + +#if !ASCON_INLINE_PERM && !ASCON_UNROLL_LOOPS + +void P(ascon_state_t* s, int nr) { PROUNDS(s, nr); } + +#endif diff --git a/lib/ascon/permutations.h b/lib/ascon/permutations.h new file mode 100644 index 0000000000..764ad78510 --- /dev/null +++ b/lib/ascon/permutations.h @@ -0,0 +1,84 @@ +#ifndef PERMUTATIONS_H_ +#define PERMUTATIONS_H_ + +#include + +#include "api.h" +#include "ascon.h" +#include "config.h" +#include "constants.h" +#include "printstate.h" +#include "round.h" + +forceinline void P12ROUNDS(ascon_state_t* s) { + ROUND(s, RC0); + ROUND(s, RC1); + ROUND(s, RC2); + ROUND(s, RC3); + ROUND(s, RC4); + ROUND(s, RC5); + ROUND(s, RC6); + ROUND(s, RC7); + ROUND(s, RC8); + ROUND(s, RC9); + ROUND(s, RCa); + ROUND(s, RCb); +} + +forceinline void P8ROUNDS(ascon_state_t* s) { + ROUND(s, RC4); + ROUND(s, RC5); + ROUND(s, RC6); + ROUND(s, RC7); + ROUND(s, RC8); + ROUND(s, RC9); + ROUND(s, RCa); + ROUND(s, RCb); +} + +forceinline void P6ROUNDS(ascon_state_t* s) { + ROUND(s, RC6); + ROUND(s, RC7); + ROUND(s, RC8); + ROUND(s, RC9); + ROUND(s, RCa); + ROUND(s, RCb); +} + +#if ASCON_INLINE_PERM && ASCON_UNROLL_LOOPS + +forceinline void P(ascon_state_t* s, int nr) { + if (nr == 12) P12ROUNDS(s); + if (nr == 8) P8ROUNDS(s); + if (nr == 6) P6ROUNDS(s); +} + +#elif !ASCON_INLINE_PERM && ASCON_UNROLL_LOOPS + +void P12(ascon_state_t* s); +void P8(ascon_state_t* s); +void P6(ascon_state_t* s); + +forceinline void P(ascon_state_t* s, int nr) { + if (nr == 12) P12(s); +#if ((defined(ASCON_AEAD_RATE) && ASCON_AEAD_RATE == 16) || \ + (defined(ASCON_HASH_ROUNDS) && ASCON_HASH_ROUNDS == 8) || \ + (defined(ASCON_PRF_ROUNDS) && ASCON_PRF_ROUNDS == 8)) + if (nr == 8) P8(s); +#endif +#if (defined(ASCON_AEAD_RATE) && ASCON_AEAD_RATE == 8) + if (nr == 6) P6(s); +#endif +} + +#elif ASCON_INLINE_PERM && !ASCON_UNROLL_LOOPS + +forceinline void P(ascon_state_t* s, int nr) { PROUNDS(s, nr); } + +#else /* !ASCON_INLINE_PERM && !ASCON_UNROLL_LOOPS */ + +void P(ascon_state_t* s, int nr); + +#endif + +#endif /* PERMUTATIONS_H_ */ diff --git a/lib/ascon/printstate.c b/lib/ascon/printstate.c new file mode 100644 index 0000000000..2f0d0bc9e5 --- /dev/null +++ b/lib/ascon/printstate.c @@ -0,0 +1,50 @@ +#ifdef ASCON_PRINT_STATE + +#include "printstate.h" + +#include +#include +#include + +#ifndef WORDTOU64 +#define WORDTOU64 +#endif + +#ifndef U64LE +#define U64LE +#endif + +void print(const char* text) { printf("%s", text); } + +void printbytes(const char* text, const uint8_t* b, uint64_t len) { + uint64_t i; + printf(" %s[%" PRIu64 "]\t= {", text, len); + for (i = 0; i < len; ++i) printf("0x%02x%s", b[i], i < len - 1 ? ", " : ""); + printf("}\n"); +} + +void printword(const char* text, const uint64_t x) { + printf("%s=0x%016" PRIx64, text, U64LE(WORDTOU64(x))); +} + +void printstate(const char* text, const ascon_state_t* s) { + int i; + printf("%s:", text); + for (i = strlen(text); i < 17; ++i) printf(" "); + printword(" x0", s->x[0]); + printword(" x1", s->x[1]); + printword(" x2", s->x[2]); + printword(" x3", s->x[3]); + printword(" x4", s->x[4]); +#ifdef ASCON_PRINT_BI + printf(" "); + printf(" x0=%08x_%08x", s->w[0][1], s->w[0][0]); + printf(" x1=%08x_%08x", s->w[1][1], s->w[1][0]); + printf(" x2=%08x_%08x", s->w[2][1], s->w[2][0]); + printf(" x3=%08x_%08x", s->w[3][1], s->w[3][0]); + printf(" x4=%08x_%08x", s->w[4][1], s->w[4][0]); +#endif + printf("\n"); +} + +#endif diff --git a/lib/ascon/printstate.h b/lib/ascon/printstate.h new file mode 100644 index 0000000000..2be5351cfa --- /dev/null +++ b/lib/ascon/printstate.h @@ -0,0 +1,33 @@ +#ifndef PRINTSTATE_H_ +#define PRINTSTATE_H_ + +#ifdef ASCON_PRINT_STATE + +#include "ascon.h" + +void print(const char* text); +void printbytes(const char* text, const uint8_t* b, uint64_t len); +void printword(const char* text, const uint64_t x); +void printstate(const char* text, const ascon_state_t* s); + +#else + +#define print(text) \ + do { \ + } while (0) + +#define printbytes(text, b, l) \ + do { \ + } while (0) + +#define printword(text, w) \ + do { \ + } while (0) + +#define printstate(text, s) \ + do { \ + } while (0) + +#endif + +#endif /* PRINTSTATE_H_ */ diff --git a/lib/ascon/round.h b/lib/ascon/round.h new file mode 100644 index 0000000000..46586af4ae --- /dev/null +++ b/lib/ascon/round.h @@ -0,0 +1,50 @@ +#ifndef ROUND_H_ +#define ROUND_H_ + +#include "ascon.h" +#include "constants.h" +#include "forceinline.h" +#include "printstate.h" +#include "word.h" + +forceinline void ROUND(ascon_state_t* s, uint8_t C) { + uint64_t xtemp; + /* round constant */ + s->x[2] ^= C; + /* s-box layer */ + s->x[0] ^= s->x[4]; + s->x[4] ^= s->x[3]; + s->x[2] ^= s->x[1]; + xtemp = s->x[0] & ~s->x[4]; + s->x[0] ^= s->x[2] & ~s->x[1]; + s->x[2] ^= s->x[4] & ~s->x[3]; + s->x[4] ^= s->x[1] & ~s->x[0]; + s->x[1] ^= s->x[3] & ~s->x[2]; + s->x[3] ^= xtemp; + s->x[1] ^= s->x[0]; + s->x[3] ^= s->x[2]; + s->x[0] ^= s->x[4]; + s->x[2] = ~s->x[2]; + /* linear layer */ + s->x[0] ^= + (s->x[0] >> 19) ^ (s->x[0] << 45) ^ (s->x[0] >> 28) ^ (s->x[0] << 36); + s->x[1] ^= + (s->x[1] >> 61) ^ (s->x[1] << 3) ^ (s->x[1] >> 39) ^ (s->x[1] << 25); + s->x[2] ^= + (s->x[2] >> 1) ^ (s->x[2] << 63) ^ (s->x[2] >> 6) ^ (s->x[2] << 58); + s->x[3] ^= + (s->x[3] >> 10) ^ (s->x[3] << 54) ^ (s->x[3] >> 17) ^ (s->x[3] << 47); + s->x[4] ^= + (s->x[4] >> 7) ^ (s->x[4] << 57) ^ (s->x[4] >> 41) ^ (s->x[4] << 23); + printstate(" round output", s); +} + +forceinline void PROUNDS(ascon_state_t* s, int nr) { + int i = START(nr); + do { + ROUND(s, RC(i)); + i += INC; + } while (i != END); +} + +#endif /* ROUND_H_ */ diff --git a/lib/ascon/word.h b/lib/ascon/word.h new file mode 100644 index 0000000000..ff98058fb8 --- /dev/null +++ b/lib/ascon/word.h @@ -0,0 +1,63 @@ +#ifndef WORD_H_ +#define WORD_H_ + +#include +#include + +#include "forceinline.h" +#include "lendian.h" + +typedef union { + uint64_t x; + uint32_t w[2]; + uint8_t b[8]; +} word_t; + +#define U64TOWORD(x) U64LE(x) +#define WORDTOU64(x) U64LE(x) +#define LOAD(b, n) LOADBYTES(b, n) +#define STORE(b, w, n) STOREBYTES(b, w, n) + +forceinline uint64_t ROR(uint64_t x, int n) { return x >> n | x << (-n & 63); } + +forceinline uint64_t KEYROT(uint64_t hi2lo, uint64_t lo2hi) { + return lo2hi << 32 | hi2lo >> 32; +} + +forceinline int NOTZERO(uint64_t a, uint64_t b) { + uint64_t result = a | b; + result |= result >> 32; + result |= result >> 16; + result |= result >> 8; + return ((((int)(result & 0xff) - 1) >> 8) & 1) - 1; +} + +forceinline uint64_t PAD(int i) { return 0x01ull << (8 * i); } + +forceinline uint64_t DSEP() { return 0x80ull << 56; } + +forceinline uint64_t PRFS_MLEN(uint64_t len) { return len << 51; } + +forceinline uint64_t CLEAR(uint64_t w, int n) { + /* undefined for n == 0 */ + uint64_t mask = ~0ull << (8 * n); + return w & mask; +} + +forceinline uint64_t MASK(int n) { + /* undefined for n == 0 */ + return ~0ull << (64 - 8 * n); +} + +forceinline uint64_t LOADBYTES(const uint8_t* bytes, int n) { + uint64_t x = 0; + memcpy(&x, bytes, n); + return U64TOWORD(x); +} + +forceinline void STOREBYTES(uint8_t* bytes, uint64_t w, int n) { + uint64_t x = WORDTOU64(w); + memcpy(bytes, &x, n); +} + +#endif /* WORD_H_ */ diff --git a/platformio.ini b/platformio.ini index 5f722e8923..01812283cf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,6 +13,11 @@ extra_configs = variants/*/platformio.ini platformio.local.ini +[env] +test_framework = unity +lib_compat_mode = off +test_build_src = yes + [arduino_base] framework = arduino monitor_speed = 115200 diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 57fee14036..9720f86a48 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -724,4 +724,4 @@ void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay sendPacket(packet, 0, delay_millis); } -} \ No newline at end of file +} diff --git a/src/Utils.cpp b/src/Utils.cpp index 186c8720a2..efc74d5206 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -9,9 +9,17 @@ namespace mesh { uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) { + if (_max <= _min) return _min; + + const uint32_t range = _max - _min; + const uint32_t limit = 0xFFFFFFFFu - (0xFFFFFFFFu % range); + uint32_t num; - random((uint8_t *) &num, sizeof(num)); - return (num % (_max - _min)) + _min; + do { + random((uint8_t *)&num, sizeof(num)); + } while (num >= limit); + + return _min + (num % range); } void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) { @@ -150,4 +158,4 @@ int Utils::parseTextParts(char* text, const char* parts[], int max_num, char sep return num; } -} \ No newline at end of file +} diff --git a/src/helpers/ArduinoHelpers.cpp b/src/helpers/ArduinoHelpers.cpp new file mode 100644 index 0000000000..2deee665fb --- /dev/null +++ b/src/helpers/ArduinoHelpers.cpp @@ -0,0 +1,218 @@ +#include + +#include + +#if defined(ESP32) + #include + #include +#elif defined(NRF52_PLATFORM) + #include +#endif + +namespace { + +struct RNGSeedBlob { + uint32_t magic; + uint16_t version; + uint16_t reserved; + uint8_t seed[32]; +}; + +static const uint32_t RNG_SEED_MAGIC = 0x474E5253; // "SRNG" +static const uint16_t RNG_SEED_VERSION = 1; + +} + +namespace mesh { + +void initHardwareRNG() { +#if defined(ESP32) + bootloader_random_enable(); +#elif defined(NRF52_PLATFORM) + NRF_RNG->TASKS_START = 1; +#elif defined(STM32_PLATFORM) + #if defined(__HAL_RCC_RNG_CLK_ENABLE) + __HAL_RCC_RNG_CLK_ENABLE(); + #endif + #if defined(RNG) && defined(RNG_CR_RNGEN) + SET_BIT(RNG->CR, RNG_CR_RNGEN); + #endif +#endif +} + +void deinitHardwareRNG() { +#if defined(ESP32) + bootloader_random_disable(); +#elif defined(NRF52_PLATFORM) + NRF_RNG->TASKS_STOP = 1; +#elif defined(STM32_PLATFORM) + #if defined(RNG) && defined(RNG_CR_RNGEN) + CLEAR_BIT(RNG->CR, RNG_CR_RNGEN); + #endif + #if defined(__HAL_RCC_RNG_CLK_DISABLE) + __HAL_RCC_RNG_CLK_DISABLE(); + #endif +#endif +} + +} + +void AsconRNG::gatherEntropy(uint8_t* dest, size_t len) { + size_t offset = 0; + while (offset < len) { + uint32_t r = getHardwareRandom32(); + size_t chunk = len - offset; + if (chunk > sizeof(r)) chunk = sizeof(r); + memcpy(dest + offset, &r, chunk); + offset += chunk; + } +} + +/** + * This is obviously slow and should not be called directly + */ +uint32_t AsconRNG::getHardwareRandom32() { + uint32_t r = 0; +#if defined(ESP32) + r = esp_random(); +#elif defined(NRF52_PLATFORM) + for (int i = 0; i < 4; i++) { + while (NRF_RNG->EVENTS_VALRDY == 0) { + } + NRF_RNG->EVENTS_VALRDY = 0; + ((uint8_t*)&r)[i] = NRF_RNG->VALUE; + } +#elif defined(STM32_PLATFORM) && defined(RNG) && defined(RNG_SR_DRDY) + uint32_t start = micros(); + while ((RNG->SR & RNG_SR_DRDY) == 0) { + if ((micros() - start) > 2000) { + uint32_t m = micros(); + uint32_t n = millis(); + r = (m << 16) ^ (n * 2654435761u); + break; + } + } + if ((RNG->SR & RNG_SR_DRDY) != 0) { + r = RNG->DR; + } +#else + uint32_t m = micros(); + uint32_t n = millis(); + r = (m << 16) ^ (n * 2654435761u); +#endif + if (_radio) { + uint32_t rv = 0; + uint8_t* rb = (uint8_t*)&rv; + rb[0] = _radio->randomByte(); + rb[1] = _radio->randomByte(); + rb[2] = _radio->randomByte(); + rb[3] = _radio->randomByte(); + r ^= rv; + } + return r; +} + +bool AsconRNG::loadSeed(uint8_t* dest, size_t len) { + if (!_fs || !_seed_path || len < 32) { + return false; + } + if (!_fs->exists(_seed_path)) { + return false; + } + + RNGSeedBlob blob; + memset(&blob, 0, sizeof(blob)); + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + File file = _fs->open(_seed_path); +#else + File file = _fs->open(_seed_path, "r"); +#endif + if (!file) { + return false; + } + + bool ok = (file.read((uint8_t*)&blob, sizeof(blob)) == (int)sizeof(blob)); + file.close(); + + if (!ok || blob.magic != RNG_SEED_MAGIC || blob.version != RNG_SEED_VERSION) { + return false; + } + + memcpy(dest, blob.seed, 32); + return true; +} + +bool AsconRNG::saveSeed() { + if (!_fs || !_seed_path || !_is_ready) { + return false; + } + + RNGSeedBlob blob; + memset(&blob, 0, sizeof(blob)); + blob.magic = RNG_SEED_MAGIC; + blob.version = RNG_SEED_VERSION; + + ascon_state_t tmp = _xof; + ascon_squeeze(&tmp, blob.seed, sizeof(blob.seed)); + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(_seed_path); + File file = _fs->open(_seed_path, FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + File file = _fs->open(_seed_path, "w"); +#else + File file = _fs->open(_seed_path, "w", true); +#endif + if (!file) { + return false; + } + + bool ok = (file.write((const uint8_t*)&blob, sizeof(blob)) == sizeof(blob)); + file.close(); + return ok; +} + +void AsconRNG::initState(const uint8_t* seed, size_t len) { + ascon_inithash(&_xof); + ascon_absorb(&_xof, seed, len); + _is_ready = true; +} + +void AsconRNG::begin() { + if (_is_ready) return; + + uint8_t seed[32]; + if (loadSeed(seed, sizeof(seed))) { + initState(seed, sizeof(seed)); + reseed(NULL, 0); + return; + } + + gatherEntropy(seed, sizeof(seed)); + initState(seed, sizeof(seed)); + saveSeed(); +} + +void AsconRNG::reseed(const uint8_t* extra, size_t extra_len) { + if (!_is_ready) { + begin(); + } + + uint8_t carry[32]; + ascon_squeeze(&_xof, carry, sizeof(carry)); + + ascon_inithash(&_xof); + ascon_absorb(&_xof, (const uint8_t*)"MC-RNG-v1", 9); + ascon_absorb(&_xof, carry, sizeof(carry)); + if (extra && extra_len > 0) { + ascon_absorb(&_xof, extra, extra_len); + } + + saveSeed(); +} + +void AsconRNG::attachPersistence(FILESYSTEM& fs, const char* seed_path) { + _fs = &fs; + _seed_path = seed_path ? seed_path : "/seed.rng"; +} diff --git a/src/helpers/ArduinoHelpers.h b/src/helpers/ArduinoHelpers.h index 97596daa31..8e8745f881 100644 --- a/src/helpers/ArduinoHelpers.h +++ b/src/helpers/ArduinoHelpers.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include class VolatileRTCClock : public mesh::RTCClock { uint32_t base_time; @@ -24,12 +27,49 @@ class ArduinoMillis : public mesh::MillisecondClock { unsigned long getMillis() override { return millis(); } }; -class StdRNG : public mesh::RNG { +namespace mesh { +void initHardwareRNG(); +void deinitHardwareRNG(); +} + +class AsconRNG : public mesh::RNG { +protected: + RadioLibWrapper* _radio; + FILESYSTEM* _fs; + const char* _seed_path; + ascon_state_t _xof; + bool _is_ready; + + uint32_t getHardwareRandom32(); + void gatherEntropy(uint8_t* dest, size_t len); + bool loadSeed(uint8_t* dest, size_t len); + bool saveSeed(); + void initState(const uint8_t* seed, size_t len); + public: - void begin(long seed) { randomSeed(seed); } + AsconRNG() + : _radio(NULL), _fs(NULL), _seed_path("/seed.rng"), _is_ready(false) { + } + + void begin(); + void reseed(const uint8_t* extra = NULL, size_t extra_len = 0); + void setRadioEntropySource(RadioLibWrapper& radio) { _radio = &radio; } + void attachPersistence(FILESYSTEM& fs, const char* seed_path = "/seed.rng"); void random(uint8_t* dest, size_t sz) override { - for (int i = 0; i < sz; i++) { - dest[i] = (::random(0, 256) & 0xFF); + if (!_is_ready) { + begin(); + } + ascon_squeeze(&_xof, dest, sz); + } +}; + +class StdRNG : public AsconRNG { +public: + void begin() { AsconRNG::begin(); } + void begin(long seed) { + if (!_is_ready) { + begin(); } + ascon_absorb(&_xof, (const uint8_t*)&seed, sizeof(seed)); } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index a42e060a4c..af1271f96e 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -57,6 +57,7 @@ class RadioLibWrapper : public mesh::Radio { virtual void setRxBoostedGainMode(bool) { } virtual bool getRxBoostedGainMode() const { return false; } + uint8_t randomByte() { return _radio->randomByte(); } }; /** @@ -70,7 +71,7 @@ class RadioNoiseListener : public mesh::RNG { void random(uint8_t* dest, size_t sz) override { for (int i = 0; i < sz; i++) { - dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF); + dest[i] = _radio->randomByte(); } } }; diff --git a/test/test_crypto/main.cpp b/test/test_crypto/main.cpp new file mode 100644 index 0000000000..3896e073d9 --- /dev/null +++ b/test/test_crypto/main.cpp @@ -0,0 +1,43 @@ +#include +#include "test_utils.h" +#include "test_main_common.h" + +static bool tests_run = false; + +extern "C" void unity_putchar_(int c) { + Serial.write((uint8_t)c); +} + +void setup(void) { + // Initialize Serial first + Serial.begin(115200); + delay(2000); + + // Output header + Serial.println("\n\nTEST_START"); + Serial.flush(); + + DBG_INFO("╔════════════════════════════════════════════════════╗"); + DBG_INFO("║ MeshCore Crypto Test Suite (Arduino) ║"); + DBG_INFO("╚════════════════════════════════════════════════════╝"); + DBG_INFO(""); + + UNITY_BEGIN(); + + run_all_tests(); + + UNITY_END(); + tests_run = true; + + Serial.println("\n"); + Serial.println("═══════════════════════════════════════════════════════"); + Serial.println("TEST_DONE"); + Serial.println("═══════════════════════════════════════════════════════"); +} + +void loop(void) { + if (tests_run) { + // Tests are complete, just loop + delay(1000); + } +} diff --git a/test/test_crypto/test_ed25519.cpp b/test/test_crypto/test_ed25519.cpp new file mode 100644 index 0000000000..6be800ad46 --- /dev/null +++ b/test/test_crypto/test_ed25519.cpp @@ -0,0 +1,362 @@ +#include +#include +#include "test_utils.h" +#include "test_vectors.h" +#include + +/** + * \brief Test: Ed25519 key generation with deterministic seed + */ + +void test_ed25519_create_keypair_deterministic(void) { + uint8_t seed[32]; + uint8_t pub1[32], prv1[64]; + uint8_t pub2[32], prv2[64]; + + fill_test_data(seed, sizeof(seed), 0xdeadbeef); + + // Generate keypair twice from same seed + ed25519_create_keypair(pub1, prv1, seed); + ed25519_create_keypair(pub2, prv2, seed); + + // Keys should be identical + TEST_ASSERT_EQUAL_UINT8_ARRAY(pub1, pub2, 32); + TEST_ASSERT_EQUAL_UINT8_ARRAY(prv1, prv2, 64); +} + +void test_ed25519_create_keypair_different_seeds(void) { + uint8_t seed1[32], seed2[32]; + uint8_t pub1[32], prv1[64]; + uint8_t pub2[32], prv2[64]; + + fill_test_data(seed1, sizeof(seed1), 0x11111111); + fill_test_data(seed2, sizeof(seed2), 0x22222222); + + ed25519_create_keypair(pub1, prv1, seed1); + ed25519_create_keypair(pub2, prv2, seed2); + + // Keys should be different + TEST_ASSERT_FALSE(buffers_equal(pub1, pub2, 32)); + TEST_ASSERT_FALSE(buffers_equal(prv1, prv2, 64)); +} + +/** + * \brief Test: Ed25519 key exchange + */ + +void test_ed25519_key_exchange_commutative(void) { + uint8_t seed_a[32], seed_b[32]; + uint8_t pub_a[32], prv_a[64]; + uint8_t pub_b[32], prv_b[64]; + uint8_t shared_ab[32], shared_ba[32]; + + fill_test_data(seed_a, sizeof(seed_a), 0xaaaaaaaa); + fill_test_data(seed_b, sizeof(seed_b), 0xbbbbbbbb); + + // Generate keypairs for Alice and Bob + ed25519_create_keypair(pub_a, prv_a, seed_a); + ed25519_create_keypair(pub_b, prv_b, seed_b); + + // Alice computes: shared_secret = her_private × Bob's_public + ed25519_key_exchange(shared_ab, pub_b, prv_a); + + // Bob computes: shared_secret = his_private × Alice's_public + ed25519_key_exchange(shared_ba, pub_a, prv_b); + + // Both should arrive at same shared secret + TEST_ASSERT_EQUAL_UINT8_ARRAY(shared_ab, shared_ba, 32); +} + +void test_ed25519_key_exchange_different_peers(void) { + uint8_t seed_a[32], seed_b[32], seed_c[32]; + uint8_t pub_a[32], prv_a[64]; + uint8_t pub_b[32], prv_b[64]; + uint8_t pub_c[32], prv_c[64]; + uint8_t shared_ab[32], shared_ac[32]; + + fill_test_data(seed_a, sizeof(seed_a), 0x11111111); + fill_test_data(seed_b, sizeof(seed_b), 0x22222222); + fill_test_data(seed_c, sizeof(seed_c), 0x33333333); + + ed25519_create_keypair(pub_a, prv_a, seed_a); + ed25519_create_keypair(pub_b, prv_b, seed_b); + ed25519_create_keypair(pub_c, prv_c, seed_c); + + ed25519_key_exchange(shared_ab, pub_b, prv_a); + ed25519_key_exchange(shared_ac, pub_c, prv_a); + + // Different peers should produce different shared secrets + TEST_ASSERT_FALSE(buffers_equal(shared_ab, shared_ac, 32)); +} + +/** + * \brief Test: Ed25519 sign/verify + */ + +void test_ed25519_sign_verify_valid_signature(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + uint8_t message[] = "Hello, World!"; + uint8_t signature[64]; + + fill_test_data(seed, sizeof(seed), 0xfeedface); + + ed25519_create_keypair(pub, prv, seed); + ed25519_sign(signature, message, sizeof(message) - 1, pub, prv); + + int result = ed25519_verify(signature, message, sizeof(message) - 1, pub); + TEST_ASSERT_EQUAL_INT(1, result); +} + +void test_ed25519_sign_verify_invalid_signature_wrong_message(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + uint8_t message1[] = "Hello, World!"; + uint8_t message2[] = "Hello, World?"; + uint8_t signature[64]; + + fill_test_data(seed, sizeof(seed), 0xfeedface); + + ed25519_create_keypair(pub, prv, seed); + ed25519_sign(signature, message1, sizeof(message1) - 1, pub, prv); + + // Verify with different message should fail + int result = ed25519_verify(signature, message2, sizeof(message2) - 1, pub); + TEST_ASSERT_EQUAL_INT(0, result); +} + +void test_ed25519_sign_verify_invalid_signature_tampered(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + uint8_t message[] = "Secret message"; + uint8_t signature[64]; + + fill_test_data(seed, sizeof(seed), 0xdeadbeef); + + ed25519_create_keypair(pub, prv, seed); + ed25519_sign(signature, message, sizeof(message) - 1, pub, prv); + + signature[0] ^= 0xFF; + + int result = ed25519_verify(signature, message, sizeof(message) - 1, pub); + TEST_ASSERT_EQUAL_INT(0, result); +} + +void test_ed25519_sign_verify_invalid_signature_wrong_key(void) { + uint8_t seed1[32], seed2[32]; + uint8_t pub1[32], prv1[64]; + uint8_t pub2[32], prv2[64]; + uint8_t message[] = "Important data"; + uint8_t signature[64]; + + fill_test_data(seed1, sizeof(seed1), 0xaaaaaaaa); + fill_test_data(seed2, sizeof(seed2), 0xbbbbbbbb); + + ed25519_create_keypair(pub1, prv1, seed1); + ed25519_create_keypair(pub2, prv2, seed2); + + // Sign with key1 + ed25519_sign(signature, message, sizeof(message) - 1, pub1, prv1); + + // Verify with key2 should fail + int result = ed25519_verify(signature, message, sizeof(message) - 1, pub2); + TEST_ASSERT_EQUAL_INT(0, result); +} + +void test_ed25519_sign_verify_empty_message(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + uint8_t message[] = {}; + uint8_t signature[64]; + + fill_test_data(seed, sizeof(seed), 0x12345678); + + ed25519_create_keypair(pub, prv, seed); + ed25519_sign(signature, message, sizeof(message), pub, prv); + + int result = ed25519_verify(signature, message, sizeof(message), pub); + TEST_ASSERT_EQUAL_INT(1, result); +} + +void test_ed25519_sign_verify_long_message(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + uint8_t message[256]; + uint8_t signature[64]; + + fill_test_data(seed, sizeof(seed), 0x99887766); + fill_test_data(message, sizeof(message), 0x11223344); + + ed25519_create_keypair(pub, prv, seed); + ed25519_sign(signature, message, sizeof(message), pub, prv); + + int result = ed25519_verify(signature, message, sizeof(message), pub); + TEST_ASSERT_EQUAL_INT(1, result); +} + +/** + * \brief Test: Known test vector for Ed25519 keypair generation (regression detection) + * + * This test verifies that the ed25519 keypair generation is stable and produces + * the same output for a known seed. The expected public key is hardcoded to detect + * any regressions in the implementation. + */ +void test_ed25519_keypair_known_vector(void) { + uint8_t pub[32], prv[64]; + + // Generate keypair from known seed + ed25519_create_keypair(pub, prv, (uint8_t*)VECTOR_ED25519_SEED); + + // Must match expected hardcoded public key (regression detection) + TEST_ASSERT_EQUAL_UINT8_ARRAY(VECTOR_ED25519_PUBLIC_KEY, pub, 32); + + // Generate again - must be identical (deterministic) + uint8_t pub2[32], prv2[64]; + ed25519_create_keypair(pub2, prv2, (uint8_t*)VECTOR_ED25519_SEED); + TEST_ASSERT_EQUAL_UINT8_ARRAY(pub, pub2, 32); + TEST_ASSERT_EQUAL_UINT8_ARRAY(prv, prv2, 64); +} + +/** + * \brief Test: Known test vector for Ed25519 signature (regression detection) + * + * This test verifies that ed25519 signature generation is stable and produces + * the same signature for a known key and message. The expected signature is + * hardcoded to detect any regressions. + */ +void test_ed25519_signature_known_vector(void) { + uint8_t pub[32], prv[64]; + + // Generate keypair from known seed + ed25519_create_keypair(pub, prv, (uint8_t*)VECTOR_ED25519_SEED); + TEST_ASSERT_EQUAL_UINT8_ARRAY(VECTOR_ED25519_PUBLIC_KEY, pub, 32); + + // Sign known message + uint8_t signature[64]; + ed25519_sign(signature, (uint8_t*)VECTOR_ED25519_MESSAGE, sizeof(VECTOR_ED25519_MESSAGE), pub, prv); + + // Must match expected hardcoded signature (regression detection) + TEST_ASSERT_EQUAL_UINT8_ARRAY(VECTOR_ED25519_SIGNATURE, signature, 64); + + // Signature must verify + int result = ed25519_verify(signature, (uint8_t*)VECTOR_ED25519_MESSAGE, sizeof(VECTOR_ED25519_MESSAGE), pub); + TEST_ASSERT_EQUAL_INT(1, result); + + // Sign again - must produce identical signature (deterministic) + uint8_t signature2[64]; + ed25519_sign(signature2, (uint8_t*)VECTOR_ED25519_MESSAGE, sizeof(VECTOR_ED25519_MESSAGE), pub, prv); + TEST_ASSERT_EQUAL_UINT8_ARRAY(signature, signature2, 64); +} + +/**\n * \\brief Benchmark: Ed25519 key generation\n */ + +void test_ed25519_benchmark_create_keypair(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + + fill_test_data(seed, sizeof(seed), 0xdeadbeef); + + BenchmarkTimer timer; + const int iterations = 100; + + timer.start(); + for (int i = 0; i < iterations; i++) { + // Use different seed for each iteration + seed[0] = (uint8_t)(i & 0xFF); + ed25519_create_keypair(pub, prv, seed); + } + timer.stop(); + + uint32_t elapsed = timer.elapsed_ms(); + DBG_INFO("Ed25519 create_keypair: %u ms for %d iterations (%.2f ms avg)", + elapsed, iterations, (elapsed * 1.0) / iterations); +} + +/** + * \brief Benchmark: Ed25519 key exchange + */ + +void test_ed25519_benchmark_key_exchange(void) { + uint8_t seed_a[32], seed_b[32]; + uint8_t pub_a[32], prv_a[64]; + uint8_t pub_b[32], prv_b[64]; + uint8_t shared[32]; + + fill_test_data(seed_a, sizeof(seed_a), 0xaaaaaaaa); + fill_test_data(seed_b, sizeof(seed_b), 0xbbbbbbbb); + + ed25519_create_keypair(pub_a, prv_a, seed_a); + ed25519_create_keypair(pub_b, prv_b, seed_b); + + BenchmarkTimer timer; + const int iterations = 500; + + timer.start(); + for (int i = 0; i < iterations; i++) { + ed25519_key_exchange(shared, pub_b, prv_a); + } + timer.stop(); + + uint32_t elapsed = timer.elapsed_ms(); + DBG_INFO("Ed25519 key_exchange: %u ms for %d iterations (%.2f ms avg)", + elapsed, iterations, (elapsed * 1.0) / iterations); +} + +/** + * \brief Benchmark: Ed25519 signing + */ + +void test_ed25519_benchmark_sign(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + uint8_t message[64]; + uint8_t signature[64]; + + fill_test_data(seed, sizeof(seed), 0xfeedface); + fill_test_data(message, sizeof(message), 0xdeadbeef); + + ed25519_create_keypair(pub, prv, seed); + + BenchmarkTimer timer; + const int iterations = 200; + + timer.start(); + for (int i = 0; i < iterations; i++) { + ed25519_sign(signature, message, sizeof(message), pub, prv); + } + timer.stop(); + + uint32_t elapsed = timer.elapsed_ms(); + DBG_INFO("Ed25519 sign: %u ms for %d iterations (%.2f ms avg)", + elapsed, iterations, (elapsed * 1.0) / iterations); +} + +/** + * \brief Benchmark: Ed25519 verification + */ + +void test_ed25519_benchmark_verify(void) { + uint8_t seed[32]; + uint8_t pub[32], prv[64]; + uint8_t message[64]; + uint8_t signature[64]; + + fill_test_data(seed, sizeof(seed), 0xcafebabe); + fill_test_data(message, sizeof(message), 0x12345678); + + ed25519_create_keypair(pub, prv, seed); + ed25519_sign(signature, message, sizeof(message), pub, prv); + + BenchmarkTimer timer; + const int iterations = 200; + + timer.start(); + for (int i = 0; i < iterations; i++) { + ed25519_verify(signature, message, sizeof(message), pub); + } + timer.stop(); + + uint32_t elapsed = timer.elapsed_ms(); + DBG_INFO("Ed25519 verify: %u ms for %d iterations (%.2f ms avg)", + elapsed, iterations, (elapsed * 1.0) / iterations); +} diff --git a/test/test_crypto/test_main_common.h b/test/test_crypto/test_main_common.h new file mode 100644 index 0000000000..076352fec7 --- /dev/null +++ b/test/test_crypto/test_main_common.h @@ -0,0 +1,94 @@ +#pragma once + +// Common test declarations for both Arduino and native platforms +// This header eliminates duplication of test function declarations + +// Test forward declarations - ED25519 tests +extern void test_ed25519_create_keypair_deterministic(void); +extern void test_ed25519_create_keypair_different_seeds(void); +extern void test_ed25519_key_exchange_commutative(void); +extern void test_ed25519_key_exchange_different_peers(void); +extern void test_ed25519_sign_verify_valid_signature(void); +extern void test_ed25519_sign_verify_invalid_signature_wrong_message(void); +extern void test_ed25519_sign_verify_invalid_signature_tampered(void); +extern void test_ed25519_sign_verify_invalid_signature_wrong_key(void); +extern void test_ed25519_sign_verify_empty_message(void); +extern void test_ed25519_sign_verify_long_message(void); +extern void test_ed25519_benchmark_create_keypair(void); +extern void test_ed25519_benchmark_key_exchange(void); +extern void test_ed25519_benchmark_sign(void); +extern void test_ed25519_benchmark_verify(void); +extern void test_ed25519_keypair_known_vector(void); +extern void test_ed25519_signature_known_vector(void); + +// Test forward declarations - Utils crypto tests +extern void test_encryptThenMAC_basic_small_payload(void); +extern void test_encryptThenMAC_various_sizes(void); +extern void test_MACThenDecrypt_valid_mac(void); +extern void test_MACThenDecrypt_invalid_mac_tampered(void); +extern void test_MACThenDecrypt_invalid_mac_ciphertext_tampered(void); +extern void test_MACThenDecrypt_wrong_shared_secret(void); +extern void test_MACThenDecrypt_max_payload(void); +extern void test_encryptThenMAC_MACThenDecrypt_roundtrip_max_payload(void); +extern void test_encryptThenMAC_MACThenDecrypt_empty_payload(void); +extern void test_MACThenDecrypt_invalid_length_too_short(void); +extern void test_encryptThenMAC_different_keys_different_output(void); +extern void test_encryptThenMAC_deterministic(void); +extern void test_encryptThenMAC_known_vector(void); +extern void test_MACThenDecrypt_known_vector(void); +extern void test_encryptThenMAC_benchmark(void); +extern void test_MACThenDecrypt_benchmark(void); +extern void test_encryptThenMAC_MACThenDecrypt_benchmark_roundtrip(void); +extern void test_AsconRNG_benchmark_entropy_vs_prng(void); +extern void test_AsconRNG_deterministic_for_same_seed(void); +extern void test_AsconRNG_stream_advances_between_calls(void); +extern void test_AsconRNG_reseed_changes_stream(void); +extern void test_AsconRNG_reseed_deterministic_for_equal_inputs(void); + +/** + * \brief Register all tests with the test framework + * Call this in your main() function after UNITY_BEGIN() + */ +inline void run_all_tests(void) { + // ED25519 tests + RUN_TEST(test_ed25519_create_keypair_deterministic); + RUN_TEST(test_ed25519_create_keypair_different_seeds); + RUN_TEST(test_ed25519_key_exchange_commutative); + RUN_TEST(test_ed25519_key_exchange_different_peers); + RUN_TEST(test_ed25519_sign_verify_valid_signature); + RUN_TEST(test_ed25519_sign_verify_invalid_signature_wrong_message); + RUN_TEST(test_ed25519_sign_verify_invalid_signature_tampered); + RUN_TEST(test_ed25519_sign_verify_invalid_signature_wrong_key); + RUN_TEST(test_ed25519_sign_verify_empty_message); + RUN_TEST(test_ed25519_sign_verify_long_message); + RUN_TEST(test_ed25519_keypair_known_vector); + RUN_TEST(test_ed25519_signature_known_vector); + RUN_TEST(test_ed25519_benchmark_create_keypair); + RUN_TEST(test_ed25519_benchmark_key_exchange); + RUN_TEST(test_ed25519_benchmark_sign); + RUN_TEST(test_ed25519_benchmark_verify); + + // Utils crypto tests + RUN_TEST(test_encryptThenMAC_basic_small_payload); + RUN_TEST(test_encryptThenMAC_various_sizes); + RUN_TEST(test_MACThenDecrypt_valid_mac); + RUN_TEST(test_MACThenDecrypt_invalid_mac_tampered); + RUN_TEST(test_MACThenDecrypt_invalid_mac_ciphertext_tampered); + RUN_TEST(test_MACThenDecrypt_wrong_shared_secret); + RUN_TEST(test_MACThenDecrypt_max_payload); + RUN_TEST(test_encryptThenMAC_MACThenDecrypt_roundtrip_max_payload); + RUN_TEST(test_encryptThenMAC_MACThenDecrypt_empty_payload); + RUN_TEST(test_MACThenDecrypt_invalid_length_too_short); + RUN_TEST(test_encryptThenMAC_different_keys_different_output); + RUN_TEST(test_encryptThenMAC_deterministic); + RUN_TEST(test_encryptThenMAC_known_vector); + RUN_TEST(test_MACThenDecrypt_known_vector); + RUN_TEST(test_encryptThenMAC_benchmark); + RUN_TEST(test_MACThenDecrypt_benchmark); + RUN_TEST(test_encryptThenMAC_MACThenDecrypt_benchmark_roundtrip); + RUN_TEST(test_AsconRNG_benchmark_entropy_vs_prng); + RUN_TEST(test_AsconRNG_deterministic_for_same_seed); + RUN_TEST(test_AsconRNG_stream_advances_between_calls); + RUN_TEST(test_AsconRNG_reseed_changes_stream); + RUN_TEST(test_AsconRNG_reseed_deterministic_for_equal_inputs); +} diff --git a/test/test_crypto/test_setup_teardown.cpp b/test/test_crypto/test_setup_teardown.cpp new file mode 100644 index 0000000000..82ac789e0e --- /dev/null +++ b/test/test_crypto/test_setup_teardown.cpp @@ -0,0 +1,18 @@ +// Common setUp and tearDown implementations for Unity test framework +// This file must be compiled for both Arduino and native builds + +extern "C" { + /** + * \brief Setup function called before each test + */ + void setUp(void) { + // Set up before each test + } + + /** + * \brief Teardown function called after each test + */ + void tearDown(void) { + // Clean up after each test + } +} diff --git a/test/test_crypto/test_utils.h b/test/test_crypto/test_utils.h new file mode 100644 index 0000000000..9ab9e0f50c --- /dev/null +++ b/test/test_crypto/test_utils.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include +#include +#include + +inline uint32_t get_time_ms() { return millis(); } + +/** + * \brief Benchmark helper: measure elapsed time in milliseconds + */ +class BenchmarkTimer { +private: + uint32_t start_time; + uint32_t end_time; + +public: + BenchmarkTimer() : start_time(0), end_time(0) {} + + void start() { + start_time = get_time_ms(); + } + + void stop() { + end_time = get_time_ms(); + } + + uint32_t elapsed_ms() const { + return (end_time >= start_time) ? (end_time - start_time) : 0; + } +}; + +/** + * \brief Fill buffer with deterministic pseudo-random data for testing + * Uses a simple LCMG for reproducibility across platforms + */ +inline void fill_test_data(uint8_t* buf, size_t len, uint32_t seed) { + uint32_t state = seed; + for (size_t i = 0; i < len; i++) { + state = (state * 1103515245 + 12345) & 0x7fffffff; + buf[i] = (uint8_t)(state >> 16); + } +} + +/** + * \brief Check if two buffers are equal + */ +inline bool buffers_equal(const uint8_t* a, const uint8_t* b, size_t len) { + for (size_t i = 0; i < len; i++) { + if (a[i] != b[i]) return false; + } + return true; +} + +// Debug output verbosity levels +enum DebugLevel { + DEBUG_SILENT = 0, + DEBUG_ERROR = 1, + DEBUG_INFO = 2, + DEBUG_VERBOSE = 3, + DEBUG_TRACE = 4 +}; + +// Global debug level (can be overridden) +#ifndef DEBUG_LEVEL + #define DEBUG_LEVEL DEBUG_VERBOSE +#endif + +/** + * \brief Debug output that goes to Serial + */ +inline void debug_print(int level, const char* format, ...) { + if (level > DEBUG_LEVEL) return; + + va_list args; + va_start(args, format); + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), format, args); + Serial.print(buffer); + va_end(args); +} + +/** + * \brief Print hex dump of buffer (with length guard for embedded systems) + */ +inline void print_hex(const uint8_t* buf, size_t len, size_t max_print = 32) { + size_t to_print = (len > max_print) ? max_print : len; + debug_print(DEBUG_TRACE, " "); + for (size_t i = 0; i < to_print; i++) { + debug_print(DEBUG_TRACE, "%02x", buf[i]); + if ((i + 1) % 16 == 0) debug_print(DEBUG_TRACE, "\n "); + } + if (len > max_print) debug_print(DEBUG_TRACE, "... (truncated, total %zu bytes)\n", len); + debug_print(DEBUG_TRACE, "\n"); +} + +// Convenience macros for different debug levels +#define DBG_ERROR(fmt, ...) debug_print(DEBUG_ERROR, fmt "\n", ##__VA_ARGS__) +#define DBG_INFO(fmt, ...) debug_print(DEBUG_INFO, fmt "\n", ##__VA_ARGS__) +#define DBG_VERBOSE(fmt, ...) debug_print(DEBUG_VERBOSE, fmt "\n", ##__VA_ARGS__) +#define DBG_TRACE(fmt, ...) debug_print(DEBUG_TRACE, fmt "\n", ##__VA_ARGS__) \ No newline at end of file diff --git a/test/test_crypto/test_utils_crypto.cpp b/test/test_crypto/test_utils_crypto.cpp new file mode 100644 index 0000000000..bb2456381e --- /dev/null +++ b/test/test_crypto/test_utils_crypto.cpp @@ -0,0 +1,543 @@ +#include +#include +#include "test_utils.h" +#include "test_vectors.h" +#include +#include +#include +#include + +using namespace mesh; + +class TestableAsconRNG : public AsconRNG { +public: + void initWithSeed(const uint8_t* seed, size_t len) { initState(seed, len); } + bool isReady() const { return _is_ready; } + void gatherEntropyForTest(uint8_t* dest, size_t len) { gatherEntropy(dest, len); } +}; + +/** + * \brief Test: encryptThenMAC basic functionality + */ +void test_encryptThenMAC_basic_small_payload(void) { + uint8_t shared_secret[32]; + uint8_t plaintext[16]; + uint8_t ciphertext[32]; // MAC (2) + ciphertext (16) + + fill_test_data(shared_secret, sizeof(shared_secret), 0xdeadbeef); + fill_test_data(plaintext, sizeof(plaintext), 0xfeedface); + + int result = Utils::encryptThenMAC(shared_secret, ciphertext, plaintext, sizeof(plaintext)); + + // Should return MAC (2 bytes) + encrypted data (16 bytes) + TEST_ASSERT_EQUAL_INT(18, result); +} + +/** + * \brief Test: encryptThenMAC with various payload sizes + */ +void test_encryptThenMAC_various_sizes(void) { + uint8_t shared_secret[16]; + uint8_t dest[256]; + uint8_t src[100]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xaaaaaaaa); + fill_test_data(src, sizeof(src), 0xbbbbbbbb); + + int result = Utils::encryptThenMAC(shared_secret, dest, src, 1); + TEST_ASSERT_EQUAL_INT(CIPHER_MAC_SIZE + 16, result); + + result = Utils::encryptThenMAC(shared_secret, dest, src, 15); + TEST_ASSERT_EQUAL_INT(CIPHER_MAC_SIZE + 16, result); + + result = Utils::encryptThenMAC(shared_secret, dest, src, 16); + TEST_ASSERT_EQUAL_INT(CIPHER_MAC_SIZE + 16, result); + + result = Utils::encryptThenMAC(shared_secret, dest, src, 17); + TEST_ASSERT_EQUAL_INT(CIPHER_MAC_SIZE + 32, result); + + result = Utils::encryptThenMAC(shared_secret, dest, src, MAX_PACKET_PAYLOAD); + int expected_enc_len = ((MAX_PACKET_PAYLOAD + 15) / 16) * 16; + TEST_ASSERT_EQUAL_INT(CIPHER_MAC_SIZE + expected_enc_len, result); +} + +/** + * \brief Test: MACThenDecrypt with valid MAC and ciphertext + */ +void test_MACThenDecrypt_valid_mac(void) { + uint8_t shared_secret[32]; + uint8_t plaintext[16]; + uint8_t encrypted[32]; + uint8_t decrypted[32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0x11111111); + fill_test_data(plaintext, sizeof(plaintext), 0x22222222); + + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, sizeof(plaintext)); + int dec_len = Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + + // Should succeed (non-zero result) + TEST_ASSERT_NOT_EQUAL(0, dec_len); + TEST_ASSERT_EQUAL_INT(((sizeof(plaintext) + 15) / 16) * 16, dec_len); + + TEST_ASSERT_EQUAL_UINT8_ARRAY(plaintext, decrypted, sizeof(plaintext)); +} + +/** + * \brief Test: MACThenDecrypt with invalid MAC (tampered) + */ +void test_MACThenDecrypt_invalid_mac_tampered(void) { + uint8_t shared_secret[16]; + uint8_t plaintext[16]; + uint8_t encrypted[32]; + uint8_t decrypted[32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0x33333333); + fill_test_data(plaintext, sizeof(plaintext), 0x44444444); + + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, sizeof(plaintext)); + encrypted[0] ^= 0xFF; + + // Decrypt and verify MAC - should fail + int dec_len = Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + + TEST_ASSERT_EQUAL_INT(0, dec_len); +} + +/** + * \brief Test: MACThenDecrypt with tampered ciphertext + */ +void test_MACThenDecrypt_invalid_mac_ciphertext_tampered(void) { + uint8_t shared_secret[16]; + uint8_t plaintext[16]; + uint8_t encrypted[32]; + uint8_t decrypted[32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0x55555555); + fill_test_data(plaintext, sizeof(plaintext), 0x66666666); + + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, sizeof(plaintext)); + encrypted[CIPHER_MAC_SIZE] ^= 0xFF; + + // Decrypt and verify MAC - should fail + int dec_len = Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + + TEST_ASSERT_EQUAL_INT(0, dec_len); +} + +/** + * \brief Test: MACThenDecrypt with wrong shared secret + */ +void test_MACThenDecrypt_wrong_shared_secret(void) { + uint8_t secret1[16], secret2[16]; + uint8_t plaintext[16]; + uint8_t encrypted[32]; + uint8_t decrypted[32]; + + fill_test_data(secret1, sizeof(secret1), 0x77777777); + fill_test_data(secret2, sizeof(secret2), 0x88888888); + fill_test_data(plaintext, sizeof(plaintext), 0x99999999); + + // Encrypt and MAC with secret1 + int enc_len = Utils::encryptThenMAC(secret1, encrypted, plaintext, sizeof(plaintext)); + + // Try to decrypt with secret2 - should fail + int dec_len = Utils::MACThenDecrypt(secret2, decrypted, encrypted, enc_len); + + TEST_ASSERT_EQUAL_INT(0, dec_len); +} + +/** + * \brief Test: MACThenDecrypt with max payload size + */ +void test_MACThenDecrypt_max_payload(void) { + uint8_t shared_secret[32]; + uint8_t plaintext[MAX_PACKET_PAYLOAD]; + uint8_t encrypted[MAX_PACKET_PAYLOAD + 32]; + uint8_t decrypted[MAX_PACKET_PAYLOAD + 32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xaabbccdd); + fill_test_data(plaintext, sizeof(plaintext), 0x11223344); + + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, sizeof(plaintext)); + int dec_len = Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + + TEST_ASSERT_NOT_EQUAL(0, dec_len); + + TEST_ASSERT_EQUAL_UINT8_ARRAY(plaintext, decrypted, sizeof(plaintext)); +} + +/** + * \brief Test: encryptThenMAC/MACThenDecrypt roundtrip with max payload + */ +void test_encryptThenMAC_MACThenDecrypt_roundtrip_max_payload(void) { + uint8_t shared_secret[32]; + uint8_t plaintext[MAX_PACKET_PAYLOAD]; + uint8_t encrypted[MAX_PACKET_PAYLOAD + 32]; + uint8_t decrypted[MAX_PACKET_PAYLOAD + 32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xdeadbeef); + fill_test_data(plaintext, sizeof(plaintext), 0xcafebabe); + + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, sizeof(plaintext)); + int dec_len = Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + + TEST_ASSERT_NOT_EQUAL(0, dec_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY(plaintext, decrypted, sizeof(plaintext)); +} + +/** + * \brief Test: Empty payload encryption/decryption + */ +void test_encryptThenMAC_MACThenDecrypt_empty_payload(void) { + uint8_t shared_secret[32]; + uint8_t plaintext[0] = {}; + uint8_t encrypted[32]; + uint8_t decrypted[32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0x12345678); + + // Real implementation: encrypting empty payload produces no ciphertext (0 bytes) + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, 0); + + // Result is just MAC (2 bytes) + 0 bytes of ciphertext = 2 bytes total + TEST_ASSERT_EQUAL_INT(CIPHER_MAC_SIZE, enc_len); + + // Try to decrypt - this will fail because length (2) <= CIPHER_MAC_SIZE (2) + int dec_len = Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + + // Real implementation rejects this as invalid + TEST_ASSERT_EQUAL_INT(0, dec_len); +} + +/** + * \brief Test: MACThenDecrypt with invalid ciphertext length (too short) + */ +void test_MACThenDecrypt_invalid_length_too_short(void) { + uint8_t shared_secret[32]; + uint8_t encrypted[5]; // Smaller than MAC size + uint8_t decrypted[32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xfeedface); + fill_test_data(encrypted, sizeof(encrypted), 0x12345678); + + // Should return 0 for invalid length + int dec_len = Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, 1); + + TEST_ASSERT_EQUAL_INT(0, dec_len); +} + +/** + * \brief Test: Different shared secrets produce different ciphertexts + */ +void test_encryptThenMAC_different_keys_different_output(void) { + uint8_t secret1[16], secret2[16]; + uint8_t plaintext[16]; + uint8_t encrypted1[32], encrypted2[32]; + + fill_test_data(secret1, sizeof(secret1), 0x11111111); + fill_test_data(secret2, sizeof(secret2), 0x22222222); + fill_test_data(plaintext, sizeof(plaintext), 0x33333333); + + int len1 = Utils::encryptThenMAC(secret1, encrypted1, plaintext, sizeof(plaintext)); + int len2 = Utils::encryptThenMAC(secret2, encrypted2, plaintext, sizeof(plaintext)); + + TEST_ASSERT_EQUAL_INT(len1, len2); + + // Ciphertexts should be different (very low probability of collision) + TEST_ASSERT_FALSE(buffers_equal(encrypted1, encrypted2, len1)); +} + +/** + * \brief Test: Same plaintext with same key produces same ciphertext (deterministic) + */ +void test_encryptThenMAC_deterministic(void) { + uint8_t shared_secret[32]; + uint8_t plaintext[16]; + uint8_t encrypted1[32], encrypted2[32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xaabbccdd); + fill_test_data(plaintext, sizeof(plaintext), 0x11223344); + + int len1 = Utils::encryptThenMAC(shared_secret, encrypted1, plaintext, sizeof(plaintext)); + int len2 = Utils::encryptThenMAC(shared_secret, encrypted2, plaintext, sizeof(plaintext)); + + TEST_ASSERT_EQUAL_INT(len1, len2); + TEST_ASSERT_EQUAL_UINT8_ARRAY(encrypted1, encrypted2, len1); +} + +/** + * \brief Test: Known test vector for encryptThenMAC (regression detection) + * + * This test verifies that AES-128 + HMAC encryption is stable and produces + * the same output for a known key and plaintext. The expected encrypted output + * (MAC + ciphertext) is hardcoded to detect any regressions in the implementation. + */ +void test_encryptThenMAC_known_vector(void) { + uint8_t result[34]; + + int len = Utils::encryptThenMAC(VECTOR_AES_KEY, result, VECTOR_AES_PLAINTEXT, sizeof(VECTOR_AES_PLAINTEXT)); + + // Should produce exactly 34 bytes (MAC 2 + ciphertext 32) + TEST_ASSERT_EQUAL_INT(34, len); + + DBG_INFO("encryptThenMAC produced %d bytes of output", len); + DBG_TRACE("Output (hex):"); + int clen = (len / sizeof(result[0])); + for (int i = 0; i < clen; i++) { + debug_print(DEBUG_TRACE,"0x%02X", result[i]); + if (i < clen - 1) debug_print(DEBUG_TRACE,", "); + if ((i + 1) % 8 == 0) debug_print(DEBUG_TRACE,"\n"); + } + debug_print(DEBUG_TRACE,"\n"); + + // Must match expected hardcoded output (regression detection) + TEST_ASSERT_EQUAL_UINT8_ARRAY(VECTOR_AES_ENCRYPTED, result, len); + + // Encrypt again - must be identical (deterministic) + uint8_t result2[34]; + int len2 = Utils::encryptThenMAC(VECTOR_AES_KEY, result2, VECTOR_AES_PLAINTEXT, sizeof(VECTOR_AES_PLAINTEXT)); + TEST_ASSERT_EQUAL_INT(len, len2); + TEST_ASSERT_EQUAL_UINT8_ARRAY(result, result2, len); +} + +/** + * \brief Test: Known test vector for MACThenDecrypt (regression detection) + * + * This test verifies that MACThenDecrypt correctly decrypts the hardcoded + * known test vector. It validates both MAC verification and decryption. + */ +void test_MACThenDecrypt_known_vector(void) { + uint8_t decrypted[32]; + + // Decrypt the known test vector + int dec_len = Utils::MACThenDecrypt(VECTOR_AES_KEY, decrypted, VECTOR_AES_ENCRYPTED, sizeof(VECTOR_AES_ENCRYPTED)); + // Should succeed (non-zero result) and return original plaintext length + TEST_ASSERT_NOT_EQUAL(0, dec_len); + DBG_INFO("MACThenDecrypt produced %d bytes of output", dec_len); + DBG_TRACE("Decrypted output (hex):"); + for (int i = 0; i < dec_len; i++) { + debug_print(DEBUG_TRACE, "0x%02X", decrypted[i]); + if (i < dec_len - 1) debug_print(DEBUG_TRACE,", "); + if ((i + 1) % 8 == 0) debug_print(DEBUG_TRACE,"\n"); + } + debug_print(DEBUG_TRACE,"\n"); + TEST_ASSERT_EQUAL_INT(32, dec_len); + + // Decrypted plaintext must match the known original plaintext (regression detection) + TEST_ASSERT_EQUAL_UINT8_ARRAY(VECTOR_AES_PLAINTEXT, decrypted, sizeof(VECTOR_AES_PLAINTEXT)); +} + +/** + * \brief Benchmark: encryptThenMAC + */ +void test_encryptThenMAC_benchmark(void) { + uint8_t shared_secret[16]; + uint8_t plaintext[MAX_PACKET_PAYLOAD]; + uint8_t encrypted[MAX_PACKET_PAYLOAD + 32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xdeadbeef); + fill_test_data(plaintext, sizeof(plaintext), 0xcafebabe); + + BenchmarkTimer timer; + const int iterations = 100; + + timer.start(); + for (int i = 0; i < iterations; i++) { + Utils::encryptThenMAC(shared_secret, encrypted, plaintext, MAX_PACKET_PAYLOAD); + } + timer.stop(); + + uint32_t elapsed = timer.elapsed_ms(); + DBG_INFO("encryptThenMAC (%d bytes): %u ms for %d iterations (%.2f ms avg)", + MAX_PACKET_PAYLOAD, elapsed, iterations, (elapsed * 1.0) / iterations); +} + +/** + * \brief Benchmark: MACThenDecrypt + */ +void test_MACThenDecrypt_benchmark(void) { + uint8_t shared_secret[16]; + uint8_t plaintext[MAX_PACKET_PAYLOAD]; + uint8_t encrypted[MAX_PACKET_PAYLOAD + 32]; + uint8_t decrypted[MAX_PACKET_PAYLOAD + 32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xfeedface); + fill_test_data(plaintext, sizeof(plaintext), 0x12345678); + + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, MAX_PACKET_PAYLOAD); + + BenchmarkTimer timer; + const int iterations = 100; + + timer.start(); + for (int i = 0; i < iterations; i++) { + Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + } + timer.stop(); + + uint32_t elapsed = timer.elapsed_ms(); + DBG_INFO("MACThenDecrypt (%d bytes): %u ms for %d iterations (%.2f ms avg)", + MAX_PACKET_PAYLOAD, elapsed, iterations, (elapsed * 1.0) / iterations); +} + +/** + * \brief Benchmark: encryptThenMAC + MACThenDecrypt roundtrip + */ +void test_encryptThenMAC_MACThenDecrypt_benchmark_roundtrip(void) { + uint8_t shared_secret[16]; + uint8_t plaintext[MAX_PACKET_PAYLOAD]; + uint8_t encrypted[MAX_PACKET_PAYLOAD + 32]; + uint8_t decrypted[MAX_PACKET_PAYLOAD + 32]; + + fill_test_data(shared_secret, sizeof(shared_secret), 0xabcdef01); + fill_test_data(plaintext, sizeof(plaintext), 0x23456789); + + BenchmarkTimer timer; + const int iterations = 500; + + timer.start(); + for (int i = 0; i < iterations; i++) { + int enc_len = Utils::encryptThenMAC(shared_secret, encrypted, plaintext, MAX_PACKET_PAYLOAD); + Utils::MACThenDecrypt(shared_secret, decrypted, encrypted, enc_len); + } + timer.stop(); + + uint32_t elapsed = timer.elapsed_ms(); + DBG_INFO("encryptThenMAC + MACThenDecrypt roundtrip (%d bytes): %u ms for %d iterations (%.2f ms avg)", + MAX_PACKET_PAYLOAD, elapsed, iterations, (elapsed * 1.0) / iterations); +} + +/** + * \brief Benchmark: compare hardware entropy gather speed vs Ascon PRNG feed speed + */ +void test_AsconRNG_benchmark_entropy_vs_prng(void) { + static const size_t CHUNK_SIZE = 256; + static const int ITERATIONS = 32; + const size_t total_bytes = CHUNK_SIZE * ITERATIONS; + + uint8_t buf[CHUNK_SIZE]; + uint8_t seed[32]; + + TestableAsconRNG hw_rng; + mesh::initHardwareRNG(); + uint32_t hw_start_us = micros(); + for (int i = 0; i < ITERATIONS; i++) { + hw_rng.gatherEntropyForTest(buf, sizeof(buf)); + } + uint32_t hw_elapsed_us = micros() - hw_start_us; + mesh::deinitHardwareRNG(); + + fill_test_data(seed, sizeof(seed), 0x5e6f7081); + TestableAsconRNG prng; + prng.initWithSeed(seed, sizeof(seed)); + uint32_t prng_start_us = micros(); + for (int i = 0; i < ITERATIONS; i++) { + prng.random(buf, sizeof(buf)); + } + uint32_t prng_elapsed_us = micros() - prng_start_us; + + TEST_ASSERT_TRUE(hw_elapsed_us > 0); + TEST_ASSERT_TRUE(prng_elapsed_us > 0); + + float hw_kb_per_s = ((float)total_bytes * 1000000.0f / (float)hw_elapsed_us) / 1024.0f; + float prng_kb_per_s = ((float)total_bytes * 1000000.0f / (float)prng_elapsed_us) / 1024.0f; + float speedup = (float)hw_elapsed_us / (float)prng_elapsed_us; + + DBG_INFO("AsconRNG entropy benchmark: %u bytes", (unsigned int)total_bytes); + DBG_INFO(" hardware entropy: %u us (%.2f KB/s)", (unsigned int)hw_elapsed_us, hw_kb_per_s); + DBG_INFO(" PRNG feed : %u us (%.2f KB/s)", (unsigned int)prng_elapsed_us, prng_kb_per_s); + DBG_INFO(" PRNG speedup : %.2fx (higher is faster)", speedup); +} + +/** + * \brief Test: AsconRNG output is deterministic for identical explicit seed + */ +void test_AsconRNG_deterministic_for_same_seed(void) { + uint8_t seed[32]; + uint8_t out1[64]; + uint8_t out2[64]; + + fill_test_data(seed, sizeof(seed), 0x1a2b3c4d); + + TestableAsconRNG rng1; + TestableAsconRNG rng2; + rng1.initWithSeed(seed, sizeof(seed)); + rng2.initWithSeed(seed, sizeof(seed)); + + rng1.random(out1, sizeof(out1)); + rng2.random(out2, sizeof(out2)); + + TEST_ASSERT_TRUE(rng1.isReady()); + TEST_ASSERT_TRUE(rng2.isReady()); + TEST_ASSERT_EQUAL_UINT8_ARRAY(out1, out2, sizeof(out1)); +} + +/** + * \brief Test: AsconRNG stream advances between sequential reads + */ +void test_AsconRNG_stream_advances_between_calls(void) { + uint8_t seed[32]; + uint8_t block1[32]; + uint8_t block2[32]; + + fill_test_data(seed, sizeof(seed), 0x2b3c4d5e); + + TestableAsconRNG rng; + rng.initWithSeed(seed, sizeof(seed)); + rng.random(block1, sizeof(block1)); + rng.random(block2, sizeof(block2)); + + // Collision is cryptographically negligible. + TEST_ASSERT_FALSE(buffers_equal(block1, block2, sizeof(block1))); +} + +/** + * \brief Test: AsconRNG reseed alters subsequent stream output + */ +void test_AsconRNG_reseed_changes_stream(void) { + uint8_t seed[32]; + uint8_t extra[20]; + uint8_t out_reseeded[32]; + uint8_t out_control[32]; + + fill_test_data(seed, sizeof(seed), 0x3c4d5e6f); + fill_test_data(extra, sizeof(extra), 0xabcdef12); + + TestableAsconRNG reseeded; + TestableAsconRNG control; + reseeded.initWithSeed(seed, sizeof(seed)); + control.initWithSeed(seed, sizeof(seed)); + + reseeded.reseed(extra, sizeof(extra)); + reseeded.random(out_reseeded, sizeof(out_reseeded)); + control.random(out_control, sizeof(out_control)); + + // Collision is cryptographically negligible. + TEST_ASSERT_FALSE(buffers_equal(out_reseeded, out_control, sizeof(out_reseeded))); +} + +/** + * \brief Test: AsconRNG reseed is deterministic when state and extra are equal + */ +void test_AsconRNG_reseed_deterministic_for_equal_inputs(void) { + uint8_t seed[32]; + uint8_t extra[24]; + uint8_t out1[48]; + uint8_t out2[48]; + + fill_test_data(seed, sizeof(seed), 0x4d5e6f70); + fill_test_data(extra, sizeof(extra), 0x1234abcd); + + TestableAsconRNG rng1; + TestableAsconRNG rng2; + rng1.initWithSeed(seed, sizeof(seed)); + rng2.initWithSeed(seed, sizeof(seed)); + + rng1.reseed(extra, sizeof(extra)); + rng2.reseed(extra, sizeof(extra)); + rng1.random(out1, sizeof(out1)); + rng2.random(out2, sizeof(out2)); + + TEST_ASSERT_EQUAL_UINT8_ARRAY(out1, out2, sizeof(out1)); +} diff --git a/test/test_crypto/test_vectors.h b/test/test_crypto/test_vectors.h new file mode 100644 index 0000000000..97f5182d26 --- /dev/null +++ b/test/test_crypto/test_vectors.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +/** + * \file test_vectors.h + * \brief Hardcoded test vectors for regression detection + * + * These are known good outputs that should never change. If they do, + * it indicates a regression in the cryptographic implementations. + * + */ + +// ============================================================================ +// ED25519 Test Vectors +// ============================================================================ + +// Input seed for ED25519 keypair generation +const uint8_t VECTOR_ED25519_SEED[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f +}; + +// Expected public key derived from VECTOR_ED25519_SEED +// Generated from the ed25519-donna implementation +// DO NOT CHANGE unless the ed25519 implementation is updated +const uint8_t VECTOR_ED25519_PUBLIC_KEY[32] = { + 0x03, 0xa1, 0x07, 0xbf, 0xf3, 0xce, 0x10, 0xbe, + 0x1d, 0x70, 0xdd, 0x18, 0xe7, 0x4b, 0xc0, 0x99, + 0x67, 0xe4, 0xd6, 0x30, 0x9b, 0xa5, 0x0d, 0x5f, + 0x1d, 0xdc, 0x86, 0x64, 0x12, 0x55, 0x31, 0xb8 +}; + +// Test message for signing +const uint8_t VECTOR_ED25519_MESSAGE[16] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f +}; + +// Expected signature for MESSAGE signed with keypair from SEED +// This must be generated from the keypair above and never change +// DO NOT CHANGE unless the ed25519 implementation is updated +const uint8_t VECTOR_ED25519_SIGNATURE[64] = { + 0x3f, 0x66, 0xbc, 0xab, 0x65, 0xb4, 0xdd, 0x46, + 0xd8, 0x51, 0x21, 0xfc, 0xec, 0x92, 0xae, 0xc9, + 0x1e, 0x0f, 0xa6, 0x9d, 0x44, 0xd1, 0x00, 0x97, + 0x0e, 0x0d, 0x24, 0xe4, 0xf6, 0x5c, 0x3e, 0x86, + 0x27, 0xa5, 0xc8, 0x41, 0x75, 0x7a, 0x51, 0x4d, + 0x84, 0x09, 0xbb, 0x0e, 0x3b, 0x0a, 0xca, 0xe9, + 0x45, 0x77, 0x25, 0x42, 0xc3, 0x45, 0xc1, 0x89, + 0xce, 0x71, 0x9f, 0xf6, 0x7f, 0x44, 0x0c, 0x07 +}; + +// ============================================================================ +// AES-128 + HMAC-SHA256 Test Vectors +// ============================================================================ + + +// Input key for AES+HMAC (32 bytes - AES-128 key size is 16 bytes, but we use 32 for HMAC) +const uint8_t VECTOR_AES_KEY[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f +}; + +// Input plaintext for encryption (16 bytes - exactly two AES blocks) +const uint8_t VECTOR_AES_PLAINTEXT[32] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f +}; + +// Expected output: MAC (2 bytes) + ciphertext (16x2 bytes) = 34 bytes total +// Format: [CIPHER_MAC_BYTE0, CIPHER_MAC_BYTE1, CIPHERTEXT_0-15] +// Generated using AES128-ECB + HMAC-SHA256 (MAC truncated to 2 bytes) +// DO NOT CHANGE unless the AES or HMAC implementation is updated +const uint8_t VECTOR_AES_ENCRYPTED[] = { + // MAC (2 bytes) - first 2 bytes of HMAC-SHA256 of ciphertext + 0x5F, 0x49, + // Ciphertext (32 bytes) - AES-128 ECB encryption of plaintext + 0x07, 0xFE, 0xEF, 0x74, 0xE1, 0xD5, 0x03, 0x6E, + 0x90, 0x0E, 0xEE, 0x11, 0x8E, 0x94, 0x92, 0x93, + 0x5B, 0xE8, 0x7E, 0x2E, 0x5B, 0x44, 0x7C, 0x94, + 0x4B, 0x21, 0xC9, 0xAF, 0x77, 0x56, 0xC0, 0xD8 +}; diff --git a/test/test_crypto/unity_config.h b/test/test_crypto/unity_config.h new file mode 100644 index 0000000000..3a71e8e1b8 --- /dev/null +++ b/test/test_crypto/unity_config.h @@ -0,0 +1,22 @@ +#ifndef UNITY_CONFIG_H +#define UNITY_CONFIG_H + +#if defined(ARDUINO) && !defined(NATIVE_PLATFORM) + // For Arduino, we need to handle this carefully since unity.h uses extern "C" + // We'll declare the functions needed but avoid including Arduino.h here + + #ifdef __cplusplus + extern "C" { + #endif + + extern void unity_putchar_(int c); + + #ifdef __cplusplus + } + #endif + + #define UNITY_OUTPUT_CHAR(c) unity_putchar_(c) + #define UNITY_OUTPUT_FLUSH() do {} while(0) +#endif + +#endif // UNITY_CONFIG_H