From 0ca44cdaea234c3f41afe23325de56afdbfae81b Mon Sep 17 00:00:00 2001 From: Nunu Date: Mon, 22 Jan 2024 23:15:26 +0100 Subject: [PATCH 01/39] encryption libs imported --- Makefile | 7 ++ app/messenger.c | 3 + external/chacha/chacha.c | 227 +++++++++++++++++++++++++++++++++++++++ external/chacha/chacha.h | 38 +++++++ 4 files changed, 275 insertions(+) create mode 100644 external/chacha/chacha.c create mode 100644 external/chacha/chacha.h diff --git a/Makefile b/Makefile index 92396f5d2..010dbfd3f 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ ENABLE_MESSENGER := 1 ENABLE_MESSENGER_DELIVERY_NOTIFICATION := 1 ENABLE_MESSENGER_NOTIFICATION := 1 ENABLE_MESSENGER_UART := 0 +ENABLE_ENCRYPTION := 1 ############################################################# @@ -163,6 +164,9 @@ ifeq ($(ENABLE_MESSENGER),1) OBJS += app/messenger.o OBJS += ui/messenger.o endif +ifeq ($(ENABLE_ENCRYPTION),1) + OBJS += external/chacha/chacha.o +endif ifeq ($(OS), Windows_NT) TOP := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) @@ -374,6 +378,9 @@ endif ifeq ($(ENABLE_MESSENGER_UART),1) CFLAGS += -DENABLE_MESSENGER_UART endif +ifeq ($(ENABLE_ENCRYPTION),1) + CFLAGS += -DENABLE_ENCRYPTION +endif LDFLAGS = ifeq ($(ENABLE_CLANG),0) diff --git a/app/messenger.c b/app/messenger.c index 35fef308c..37eb44a93 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -15,6 +15,9 @@ #include "driver/system.h" #include "app/messenger.h" #include "ui/ui.h" +#ifdef ENABLE_ENCRYPTION + #include "external/chacha/chacha.h" +#endif #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) #include "driver/uart.h" diff --git a/external/chacha/chacha.c b/external/chacha/chacha.c new file mode 100644 index 000000000..ac8451d37 --- /dev/null +++ b/external/chacha/chacha.c @@ -0,0 +1,227 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#include "chacha.h" + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((unsigned char)(v) & U8C(0xFF)) +#define U32V(v) ((uint32_t)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#if (USE_UNALIGNED == 1) +#define U8TO32_LITTLE(p) \ + (*((uint32_t *)(p))) +#define U32TO8_LITTLE(p, v) \ + do { \ + *((uint32_t *)(p)) = v; \ + } while (0) +#else +#define U8TO32_LITTLE(p) \ + (((uint32_t)((p)[0]) ) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) +#endif + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +void +chacha_keysetup(struct chacha_ctx *x,const unsigned char *k,uint32_t kbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +void +chacha_ivsetup(struct chacha_ctx *x, const unsigned char *iv, const unsigned char *counter) +{ + x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0); + //x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4); + x->input[13] = U8TO32_LITTLE(iv + 0); + x->input[14] = U8TO32_LITTLE(iv + 4); + x->input[15] = U8TO32_LITTLE(iv + 8); +} + +void +chacha_encrypt_bytes(struct chacha_ctx *x,const unsigned char *m,unsigned char *c,uint32_t bytes) +{ + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + unsigned char *ctarget = NULL; + unsigned char tmp[64]; + u_int8_t i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { +#if (USE_MEMCPY == 1) + memcpy(tmp, m, bytes); +#else + for (i = 0;i < bytes;++i) tmp[i] = m[i]; +#endif + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { +#if (USE_MEMCPY == 1) + memcpy(ctarget, c, bytes); +#else + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; +#endif + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; + m += 64; + } +} diff --git a/external/chacha/chacha.h b/external/chacha/chacha.h new file mode 100644 index 000000000..3892001d1 --- /dev/null +++ b/external/chacha/chacha.h @@ -0,0 +1,38 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#ifndef CHACHA_H +#define CHACHA_H + +#include +#include +#include +#include + +#define CHACHA_MINKEYLEN 16 +#define CHACHA_NONCELEN 8 +#define CHACHA_CTRLEN 8 +#define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) +#define CHACHA_BLOCKLEN 64 + +/* use memcpy() to copy blocks of memory (typically faster) */ +#define USE_MEMCPY 1 +/* use unaligned little-endian load/store (can be faster) */ +#define USE_UNALIGNED 0 + +struct chacha_ctx { + uint32_t input[16]; +}; + +void chacha_keysetup(struct chacha_ctx *x, const unsigned char *k, + uint32_t kbits); +void chacha_ivsetup(struct chacha_ctx *x, const unsigned char *iv, + const unsigned char *ctr); +void chacha_encrypt_bytes(struct chacha_ctx *x, const unsigned char *m, + unsigned char *c, uint32_t bytes); + +#endif /* CHACHA_H */ + From 40a6b408def826045cf671f3cc8c01837e31fabd Mon Sep 17 00:00:00 2001 From: Nunu Date: Tue, 23 Jan 2024 11:39:06 +0100 Subject: [PATCH 02/39] initial enc send rec --- Makefile | 2 +- app/messenger.c | 104 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 010dbfd3f..267ebb4b4 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ENABLE_LTO := 1 # ---- STOCK QUANSHENG FERATURES ---- ENABLE_UART := 1 ENABLE_AIRCOPY := 0 -ENABLE_FMRADIO := 1 +ENABLE_FMRADIO := 0 ENABLE_NOAA := 0 ENABLE_VOICE := 0 ENABLE_VOX := 1 diff --git a/app/messenger.c b/app/messenger.c index 37eb44a93..f80c885d0 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -18,6 +18,7 @@ #ifdef ENABLE_ENCRYPTION #include "external/chacha/chacha.h" #endif +#include "debugging.h" #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) #include "driver/uart.h" @@ -64,6 +65,23 @@ uint8_t hasNewMessage = 0; uint8_t keyTickCounter = 0; +struct chacha_ctx ctx; + + unsigned char key[32] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f + }; + + unsigned char nonce[12] = { + 0x07, 0x00, 0x00, 0x00, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 + }; + + // unsigned char pt[114]; + + // unsigned char ct[114]; + + // ----------------------------------------------------- void MSG_FSKSendData() { @@ -559,10 +577,48 @@ void MSG_Send(const char txMessage[TX_MSG_LENGTH], bool bServiceMessage) { // first 20 byte sync, msg type and ID msgFSKBuffer[0] = 'M'; msgFSKBuffer[1] = 'S'; + + char encryptedTxMessage[TX_MSG_LENGTH]; + // + unsigned char keystream[CHACHA_BLOCKLEN]; + // const unsigned char one[4] = { 1, 0, 0, 0 }; + + // init + memset(&ctx, 0, sizeof(ctx)); + memset(encryptedTxMessage, 0, sizeof(encryptedTxMessage)); + chacha_keysetup(&ctx, key, 256); + + /* initialize keystream and generate poly1305 key */ + memset(keystream, 0, sizeof(keystream)); + chacha_ivsetup(&ctx, nonce, NULL); + chacha_encrypt_bytes(&ctx, keystream, keystream,sizeof(keystream)); + + /* crypt data */ + // // consider crypt-short version + // chacha_ivsetup(&ctx, nonce, one); + // chacha_encrypt_bytes(&ctx, (unsigned char *)pt, + // (unsigned char *)ct, 114); + + uint8_t i; + /* crypt data short*/ + // <--- use this as we have message short enough and this makes code much smaller + + // char String[40]; + for (i = 0; i < TX_MSG_LENGTH; i++) { + // sprintf(String, "i:%dc:%d\n", i, (u_int8_t)txMessage[i]); + // LogUart(String); + ((unsigned char *)encryptedTxMessage)[i] = + ((unsigned char *)txMessage)[i] ^ keystream[32 + i]; + // sprintf(String, "enc-> i:%dc:%d\n", i, (u_int8_t)encryptedTxMessage[i]); + // LogUart(String); + } - // next 20 for msg - memcpy(msgFSKBuffer + 2, txMessage, TX_MSG_LENGTH); + + // chacha_ivsetup(struct chacha_ctx *x, const unsigned char *iv, const unsigned char *counter) + // next 20 for msg + memcpy(msgFSKBuffer + 2, encryptedTxMessage, TX_MSG_LENGTH); + // CRC ? ToDo msgFSKBuffer[MAX_RX_MSG_LENGTH - 1] = '\0'; @@ -642,9 +698,9 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { for (uint16_t i = 0; i < count; i++) { const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F); if (gFSKWriteIndex < sizeof(msgFSKBuffer)) - msgFSKBuffer[gFSKWriteIndex++] = validate_char((word >> 0) & 0xff); + msgFSKBuffer[gFSKWriteIndex++] = (word >> 0) & 0xff; if (gFSKWriteIndex < sizeof(msgFSKBuffer)) - msgFSKBuffer[gFSKWriteIndex++] = validate_char((word >> 8) & 0xff); + msgFSKBuffer[gFSKWriteIndex++] = (word >> 8) & 0xff; } SYSTEM_DelayMs(10); @@ -681,7 +737,45 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { } else { - snprintf(rxMessage[3], TX_MSG_LENGTH + 2, "< %s", &msgFSKBuffer[2]); + unsigned char keystream[CHACHA_BLOCKLEN]; + char dencryptedTxMessage[TX_MSG_LENGTH]; + + // const unsigned char one[4] = { 1, 0, 0, 0 }; + + // init + memset(&ctx, 0, sizeof(ctx)); + chacha_keysetup(&ctx, key, 256); + + /* initialize keystream and generate poly1305 key */ + memset(keystream, 0, sizeof(keystream)); + chacha_ivsetup(&ctx, nonce, NULL); + chacha_encrypt_bytes(&ctx, keystream, keystream,sizeof(keystream)); + + /* crypt data */ + // // consider crypt-short version + // chacha_ivsetup(&ctx, nonce, one); + // chacha_encrypt_bytes(&ctx, (unsigned char *)pt, + // (unsigned char *)ct, 114); + + uint8_t i; + /* crypt data short*/ + // <--- use this as we have message short enough and this makes code much smaller + // char String[40]; + for (i = 0; i < TX_MSG_LENGTH; i++) { + // sprintf(String, "enc rec-> i:%dc:%d\n", i, (u_int8_t)msgFSKBuffer[i+2]); + // LogUart(String); + ((unsigned char *)dencryptedTxMessage)[i] = + ((unsigned char *)msgFSKBuffer)[i+2] ^ keystream[32 + i]; + // sprintf(String, "dec rec-> i:%dc:%d\n", i, (u_int8_t)dencryptedTxMessage[i]); + // LogUart(String); + } + + // chacha_ivsetup(struct chacha_ctx *x, const unsigned char *iv, const unsigned char *counter) + + + + snprintf(rxMessage[3], TX_MSG_LENGTH + 2, "< %s", dencryptedTxMessage); + // sprintf(rxMessage[3], "< %s", dencryptedTxMessage); } #ifdef ENABLE_MESSENGER_UART From ed70f529a295969595ba7fff34579e76d777c823 Mon Sep 17 00:00:00 2001 From: Nunu Date: Tue, 23 Jan 2024 13:13:21 +0100 Subject: [PATCH 03/39] moved logic to new library --- Makefile | 1 + app/messenger.c | 102 +++++++----------------------------------------- app/messenger.h | 2 +- helper/crypto.c | 42 ++++++++++++++++++++ helper/crypto.h | 18 +++++++++ 5 files changed, 76 insertions(+), 89 deletions(-) create mode 100644 helper/crypto.c create mode 100644 helper/crypto.h diff --git a/Makefile b/Makefile index 267ebb4b4..38d9c5fc3 100644 --- a/Makefile +++ b/Makefile @@ -166,6 +166,7 @@ ifeq ($(ENABLE_MESSENGER),1) endif ifeq ($(ENABLE_ENCRYPTION),1) OBJS += external/chacha/chacha.o + OBJS += helper/crypto.o endif ifeq ($(OS), Windows_NT) diff --git a/app/messenger.c b/app/messenger.c index f80c885d0..606814bca 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -16,9 +16,8 @@ #include "app/messenger.h" #include "ui/ui.h" #ifdef ENABLE_ENCRYPTION - #include "external/chacha/chacha.h" + #include "helper/crypto.h" #endif -#include "debugging.h" #if defined(ENABLE_UART) && defined(ENABLE_UART_DEBUG) #include "driver/uart.h" @@ -65,22 +64,15 @@ uint8_t hasNewMessage = 0; uint8_t keyTickCounter = 0; -struct chacha_ctx ctx; - - unsigned char key[32] = { - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f - }; - - unsigned char nonce[12] = { - 0x07, 0x00, 0x00, 0x00, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 - }; - - // unsigned char pt[114]; - - // unsigned char ct[114]; +unsigned char key[32] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f +}; +unsigned char nonce[12] = { + 0x07, 0x00, 0x00, 0x00, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 +}; // ----------------------------------------------------- @@ -560,7 +552,7 @@ void moveUP(char (*rxMessages)[MAX_RX_MSG_LENGTH + 2]) { memset(rxMessages[3], 0, sizeof(rxMessages[3])); } -void MSG_Send(const char txMessage[TX_MSG_LENGTH], bool bServiceMessage) { +void MSG_Send(char txMessage[TX_MSG_LENGTH], bool bServiceMessage) { if ( msgStatus != READY ) return; @@ -579,42 +571,10 @@ void MSG_Send(const char txMessage[TX_MSG_LENGTH], bool bServiceMessage) { msgFSKBuffer[1] = 'S'; char encryptedTxMessage[TX_MSG_LENGTH]; - // - unsigned char keystream[CHACHA_BLOCKLEN]; - // const unsigned char one[4] = { 1, 0, 0, 0 }; - - // init - memset(&ctx, 0, sizeof(ctx)); + memset(encryptedTxMessage, 0, sizeof(encryptedTxMessage)); - chacha_keysetup(&ctx, key, 256); - - /* initialize keystream and generate poly1305 key */ - memset(keystream, 0, sizeof(keystream)); - chacha_ivsetup(&ctx, nonce, NULL); - chacha_encrypt_bytes(&ctx, keystream, keystream,sizeof(keystream)); - - /* crypt data */ - // // consider crypt-short version - // chacha_ivsetup(&ctx, nonce, one); - // chacha_encrypt_bytes(&ctx, (unsigned char *)pt, - // (unsigned char *)ct, 114); - - uint8_t i; - /* crypt data short*/ - // <--- use this as we have message short enough and this makes code much smaller - - // char String[40]; - for (i = 0; i < TX_MSG_LENGTH; i++) { - // sprintf(String, "i:%dc:%d\n", i, (u_int8_t)txMessage[i]); - // LogUart(String); - ((unsigned char *)encryptedTxMessage)[i] = - ((unsigned char *)txMessage)[i] ^ keystream[32 + i]; - // sprintf(String, "enc-> i:%dc:%d\n", i, (u_int8_t)encryptedTxMessage[i]); - // LogUart(String); - } - - // chacha_ivsetup(struct chacha_ctx *x, const unsigned char *iv, const unsigned char *counter) + CRYPTO_Crypt(txMessage, TX_MSG_LENGTH, encryptedTxMessage, nonce, key, 256); // next 20 for msg memcpy(msgFSKBuffer + 2, encryptedTxMessage, TX_MSG_LENGTH); @@ -737,45 +697,11 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { } else { - unsigned char keystream[CHACHA_BLOCKLEN]; - char dencryptedTxMessage[TX_MSG_LENGTH]; - - // const unsigned char one[4] = { 1, 0, 0, 0 }; - - // init - memset(&ctx, 0, sizeof(ctx)); - chacha_keysetup(&ctx, key, 256); - - /* initialize keystream and generate poly1305 key */ - memset(keystream, 0, sizeof(keystream)); - chacha_ivsetup(&ctx, nonce, NULL); - chacha_encrypt_bytes(&ctx, keystream, keystream,sizeof(keystream)); - - /* crypt data */ - // // consider crypt-short version - // chacha_ivsetup(&ctx, nonce, one); - // chacha_encrypt_bytes(&ctx, (unsigned char *)pt, - // (unsigned char *)ct, 114); - - uint8_t i; - /* crypt data short*/ - // <--- use this as we have message short enough and this makes code much smaller - // char String[40]; - for (i = 0; i < TX_MSG_LENGTH; i++) { - // sprintf(String, "enc rec-> i:%dc:%d\n", i, (u_int8_t)msgFSKBuffer[i+2]); - // LogUart(String); - ((unsigned char *)dencryptedTxMessage)[i] = - ((unsigned char *)msgFSKBuffer)[i+2] ^ keystream[32 + i]; - // sprintf(String, "dec rec-> i:%dc:%d\n", i, (u_int8_t)dencryptedTxMessage[i]); - // LogUart(String); - } - - // chacha_ivsetup(struct chacha_ctx *x, const unsigned char *iv, const unsigned char *counter) - + char dencryptedTxMessage[TX_MSG_LENGTH]; + CRYPTO_Crypt(&msgFSKBuffer[2], TX_MSG_LENGTH, dencryptedTxMessage, nonce, key, 256); snprintf(rxMessage[3], TX_MSG_LENGTH + 2, "< %s", dencryptedTxMessage); - // sprintf(rxMessage[3], "< %s", dencryptedTxMessage); } #ifdef ENABLE_MESSENGER_UART diff --git a/app/messenger.h b/app/messenger.h index 47f55c7be..034fe812a 100644 --- a/app/messenger.h +++ b/app/messenger.h @@ -34,7 +34,7 @@ void MSG_EnableRX(const bool enable); void MSG_StorePacket(const uint16_t interrupt_bits); void MSG_Init(); void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld); -void MSG_Send(const char txMessage[TX_MSG_LENGTH], bool bServiceMessage); +void MSG_Send(char txMessage[TX_MSG_LENGTH], bool bServiceMessage); #endif diff --git a/helper/crypto.c b/helper/crypto.c new file mode 100644 index 000000000..5146bf76c --- /dev/null +++ b/helper/crypto.c @@ -0,0 +1,42 @@ +/* Copyright 2024 kamilsss655 + * https://github.com/kamilsss655 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "crypto.h" +#include "external/chacha/chacha.h" + +// Used for both encryption and decryption +void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const void *key, int key_len) +{ + struct chacha_ctx ctx; + unsigned char keystream[CHACHA_BLOCKLEN]; + char String[40]; + + memset(&ctx, 0, sizeof(ctx)); + chacha_keysetup(&ctx, key, key_len); + + // init keystream and generate key + memset(keystream, 0, sizeof(keystream)); + chacha_ivsetup(&ctx, nonce, NULL); + chacha_encrypt_bytes(&ctx, keystream, keystream,sizeof(keystream)); + + // crypt data, only works for input_len <= 32 + for (uint8_t i = 0; i < input_len; i++) { + ((unsigned char *)output)[i] = + ((unsigned char *)input)[i] ^ keystream[32 + i]; + } +} + + \ No newline at end of file diff --git a/helper/crypto.h b/helper/crypto.h new file mode 100644 index 000000000..748e0fe9f --- /dev/null +++ b/helper/crypto.h @@ -0,0 +1,18 @@ +/* Copyright 2024 kamilsss655 + * https://github.com/kamilsss655 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Used for both encryption and decryption +void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const void *key, int key_len); \ No newline at end of file From 56e346fa2b440ca0cb182630c033199546a13803 Mon Sep 17 00:00:00 2001 From: Nunu Date: Tue, 23 Jan 2024 15:01:54 +0100 Subject: [PATCH 04/39] working dataPacket type --- app/messenger.c | 72 +++++++++++++++++-------------------------------- app/messenger.h | 37 +++++++++++++++++++++++++ helper/crypto.c | 2 +- 3 files changed, 63 insertions(+), 48 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index 606814bca..1db39a371 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -23,12 +23,6 @@ #include "driver/uart.h" #endif -typedef enum MsgStatus { - READY, - SENDING, - RECEIVING, -} MsgStatus; - const uint8_t MSG_BUTTON_STATE_HELD = 1 << 1; const uint8_t MSG_BUTTON_EVENT_SHORT = 0; @@ -56,7 +50,9 @@ KeyboardType keyboardType = UPPERCASE; MsgStatus msgStatus = READY; -uint8_t msgFSKBuffer[MSG_HEADER_LENGTH + MAX_RX_MSG_LENGTH]; +// uint8_t msgFSKBuffer[MSG_HEADER_LENGTH + MAX_RX_MSG_LENGTH]; + +union DataPacket dataPacket; uint16_t gErrorsDuringMSG; @@ -280,9 +276,8 @@ void MSG_FSKSendData() { SYSTEM_DelayMs(100); { // load the entire packet data into the TX FIFO buffer - const uint16_t len_buff = (MSG_HEADER_LENGTH + MAX_RX_MSG_LENGTH); - for (size_t i = 0, j = 0; i < len_buff; i += 2, j++) { - BK4819_WriteRegister(BK4819_REG_5F, (msgFSKBuffer[i + 1] << 8) | msgFSKBuffer[i]); + for (size_t i = 0, j = 0; i < sizeof(dataPacket.serializedArray); i += 2, j++) { + BK4819_WriteRegister(BK4819_REG_5F, (dataPacket.serializedArray[i + 1] << 8) | dataPacket.serializedArray[i]); } } @@ -563,29 +558,15 @@ void MSG_Send(char txMessage[TX_MSG_LENGTH], bool bServiceMessage) { RADIO_SetVfoState(VFO_STATE_NORMAL); BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, true); - memset(msgFSKBuffer, 0, sizeof(msgFSKBuffer)); + memset(dataPacket.serializedArray, 0, sizeof(dataPacket.serializedArray)); + + dataPacket.unencrypted.header = MESSAGE_PACKET; - // ? ToDo - // first 20 byte sync, msg type and ID - msgFSKBuffer[0] = 'M'; - msgFSKBuffer[1] = 'S'; - char encryptedTxMessage[TX_MSG_LENGTH]; memset(encryptedTxMessage, 0, sizeof(encryptedTxMessage)); - CRYPTO_Crypt(txMessage, TX_MSG_LENGTH, encryptedTxMessage, nonce, key, 256); - - // next 20 for msg - memcpy(msgFSKBuffer + 2, encryptedTxMessage, TX_MSG_LENGTH); - - // CRC ? ToDo - - msgFSKBuffer[MAX_RX_MSG_LENGTH - 1] = '\0'; - msgFSKBuffer[MAX_RX_MSG_LENGTH + 0] = 'I'; - msgFSKBuffer[MAX_RX_MSG_LENGTH + 1] = 'D'; - msgFSKBuffer[MAX_RX_MSG_LENGTH + 2] = '0'; - msgFSKBuffer[(MSG_HEADER_LENGTH + MAX_RX_MSG_LENGTH) - 1] = '#'; + CRYPTO_Crypt(txMessage, TX_MSG_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); BK4819_DisableDTMF(); @@ -648,7 +629,7 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { if (rx_sync) { gFSKWriteIndex = 0; - memset(msgFSKBuffer, 0, sizeof(msgFSKBuffer)); + memset(dataPacket.serializedArray, 0, sizeof(dataPacket.serializedArray)); msgStatus = RECEIVING; } @@ -657,10 +638,10 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { const uint16_t count = BK4819_ReadRegister(BK4819_REG_5E) & (7u << 0); // almost full threshold for (uint16_t i = 0; i < count; i++) { const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F); - if (gFSKWriteIndex < sizeof(msgFSKBuffer)) - msgFSKBuffer[gFSKWriteIndex++] = (word >> 0) & 0xff; - if (gFSKWriteIndex < sizeof(msgFSKBuffer)) - msgFSKBuffer[gFSKWriteIndex++] = (word >> 8) & 0xff; + if (gFSKWriteIndex < sizeof(dataPacket.serializedArray)) + dataPacket.serializedArray[gFSKWriteIndex++] = (word >> 0) & 0xff; + if (gFSKWriteIndex < sizeof(dataPacket.serializedArray)) + dataPacket.serializedArray[gFSKWriteIndex++] = (word >> 8) & 0xff; } SYSTEM_DelayMs(10); @@ -678,34 +659,31 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { if (gFSKWriteIndex > 2) { // If there's three 0x1b bytes, then it's a service message - if (msgFSKBuffer[2] == 0x1b && msgFSKBuffer[3] == 0x1b && msgFSKBuffer[4] == 0x1b) { + if (dataPacket.unencrypted.header == ACK_PACKET) { #ifdef ENABLE_MESSENGER_DELIVERY_NOTIFICATION - // If the next 4 bytes are "RCVD", then it's a delivery notification - if (msgFSKBuffer[5] == 'R' && msgFSKBuffer[6] == 'C' && msgFSKBuffer[7] == 'V' && msgFSKBuffer[8] == 'D') { - #ifdef ENABLE_MESSENGER_UART - UART_printf("SVC Date: Tue, 23 Jan 2024 16:04:29 +0100 Subject: [PATCH 05/39] works with static key and nonce, messy --- app/messenger.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++-- app/messenger.h | 6 ++-- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index 1db39a371..1e13788c5 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -547,6 +547,71 @@ void moveUP(char (*rxMessages)[MAX_RX_MSG_LENGTH + 2]) { memset(rxMessages[3], 0, sizeof(rxMessages[3])); } +void MSG_SendPacket(union DataPacket packet) { + + if ( msgStatus != READY ) return; + + if ( strlen(packet.unencrypted.payload) > 0 && (TX_freq_check(gCurrentVfo->pTX->Frequency) == 0) ) { + + msgStatus = SENDING; + + RADIO_SetVfoState(VFO_STATE_NORMAL); + BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, true); + + memset(dataPacket.serializedArray, 0, sizeof(dataPacket.serializedArray)); + + // later refactor to not use global state but pass dataPacket type everywhere + dataPacket = packet; + + if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ + // char encryptedTxMessage[TX_MSG_LENGTH]; + + // memset(encryptedTxMessage, 0, sizeof(encryptedTxMessage)); + + CRYPTO_Crypt(packet.encrypted.ciphertext, TX_MSG_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); + } + + BK4819_DisableDTMF(); + + RADIO_SetTxParameters(); + FUNCTION_Select(FUNCTION_TRANSMIT); + SYSTEM_DelayMs(500); + // BK4819_PlayRogerNormal(98); + // BK4819_PlayRogerMDC(); + SYSTEM_DelayMs(100); + + BK4819_ExitTxMute(); + + MSG_FSKSendData(); + + SYSTEM_DelayMs(100); + + APP_EndTransmission(); + // this must be run after end of TX, otherwise radio will still TX transmit without even RED LED on + FUNCTION_Select(FUNCTION_FOREGROUND); + + RADIO_SetVfoState(VFO_STATE_NORMAL); + + BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false); + + MSG_EnableRX(true); + if (packet.unencrypted.header != ACK_PACKET) { + moveUP(rxMessage); + sprintf(rxMessage[3], "> %s", packet.encrypted.ciphertext); + memset(lastcMessage, 0, sizeof(lastcMessage)); + memcpy(lastcMessage, packet.encrypted.ciphertext, TX_MSG_LENGTH); + cIndex = 0; + prevKey = 0; + prevLetter = 0; + memset(cMessage, 0, sizeof(cMessage)); + } + msgStatus = READY; + + } else { + AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL); + } +} + void MSG_Send(char txMessage[TX_MSG_LENGTH], bool bServiceMessage) { if ( msgStatus != READY ) return; @@ -670,7 +735,7 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { #endif } else { moveUP(rxMessage); - if (dataPacket.unencrypted.header != MESSAGE_PACKET) { + if (dataPacket.unencrypted.header >= INVALID_PACKET) { snprintf(rxMessage[3], TX_MSG_LENGTH + 2, "? unknown msg format!"); } else @@ -703,7 +768,9 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { gFSKWriteIndex = 0; // Transmit a message to the sender that we have received the message (Unless it's a service message) if (dataPacket.unencrypted.header!=ACK_PACKET) { - MSG_Send("\x1b\x1b\x1bRCVD ", true); + dataPacket.unencrypted.header=ACK_PACKET; + MSG_SendPacket(dataPacket); + // MSG_Send("\x1b\x1b\x1bRCVD ", true); } } } @@ -818,7 +885,11 @@ void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { break;*/ case KEY_MENU: // Send message - MSG_Send(cMessage, false); + // MSG_Send(cMessage, false); + memset(dataPacket.serializedArray,0,sizeof(dataPacket.serializedArray)); + dataPacket.unencrypted.header=ENCRYPTED_MESSAGE_PACKET; + memcpy(dataPacket.unencrypted.payload, cMessage, sizeof(dataPacket.unencrypted.payload)); + MSG_SendPacket(dataPacket); break; case KEY_EXIT: gRequestDisplayScreen = DISPLAY_MAIN; diff --git a/app/messenger.h b/app/messenger.h index a23e15e6e..a88b87bdb 100644 --- a/app/messenger.h +++ b/app/messenger.h @@ -39,7 +39,8 @@ typedef enum MsgStatus { typedef enum PacketType { MESSAGE_PACKET, ENCRYPTED_MESSAGE_PACKET, - ACK_PACKET + ACK_PACKET, + INVALID_PACKET } PacketType; enum { @@ -59,7 +60,7 @@ union DataPacket struct{ uint8_t header; - uint8_t payload[PAYLOAD_LENGTH]; + char payload[PAYLOAD_LENGTH]; // uint8_t signature[SIGNATURE_LENGTH]; } unencrypted; // header + payload + nonce @@ -72,6 +73,7 @@ void MSG_StorePacket(const uint16_t interrupt_bits); void MSG_Init(); void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld); void MSG_Send(char txMessage[TX_MSG_LENGTH], bool bServiceMessage); +void MSG_SendPacket(union DataPacket packet); #endif From 3e6ca02d9075f535a755a9bc6469cf37566e5349 Mon Sep 17 00:00:00 2001 From: Nunu Date: Tue, 23 Jan 2024 17:45:56 +0100 Subject: [PATCH 06/39] cleanup --- Makefile | 2 +- app/messenger.c | 74 ++++--------------------------------------------- app/messenger.h | 2 +- 3 files changed, 7 insertions(+), 71 deletions(-) diff --git a/Makefile b/Makefile index 38d9c5fc3..1d99e32e4 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ENABLE_LTO := 1 # ---- STOCK QUANSHENG FERATURES ---- ENABLE_UART := 1 ENABLE_AIRCOPY := 0 -ENABLE_FMRADIO := 0 +ENABLE_FMRADIO := 1 ENABLE_NOAA := 0 ENABLE_VOICE := 0 ENABLE_VOX := 1 diff --git a/app/messenger.c b/app/messenger.c index 1e13788c5..c2c3b2f8d 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -50,8 +50,6 @@ KeyboardType keyboardType = UPPERCASE; MsgStatus msgStatus = READY; -// uint8_t msgFSKBuffer[MSG_HEADER_LENGTH + MAX_RX_MSG_LENGTH]; - union DataPacket dataPacket; uint16_t gErrorsDuringMSG; @@ -567,7 +565,7 @@ void MSG_SendPacket(union DataPacket packet) { // char encryptedTxMessage[TX_MSG_LENGTH]; // memset(encryptedTxMessage, 0, sizeof(encryptedTxMessage)); - + CRYPTO_Crypt(packet.encrypted.ciphertext, TX_MSG_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); } @@ -612,69 +610,6 @@ void MSG_SendPacket(union DataPacket packet) { } } -void MSG_Send(char txMessage[TX_MSG_LENGTH], bool bServiceMessage) { - - if ( msgStatus != READY ) return; - - if ( strlen(txMessage) > 0 && (TX_freq_check(gCurrentVfo->pTX->Frequency) == 0) ) { - - msgStatus = SENDING; - - RADIO_SetVfoState(VFO_STATE_NORMAL); - BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, true); - - memset(dataPacket.serializedArray, 0, sizeof(dataPacket.serializedArray)); - - dataPacket.unencrypted.header = MESSAGE_PACKET; - - char encryptedTxMessage[TX_MSG_LENGTH]; - - memset(encryptedTxMessage, 0, sizeof(encryptedTxMessage)); - - CRYPTO_Crypt(txMessage, TX_MSG_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); - - BK4819_DisableDTMF(); - - RADIO_SetTxParameters(); - FUNCTION_Select(FUNCTION_TRANSMIT); - SYSTEM_DelayMs(500); - // BK4819_PlayRogerNormal(98); - // BK4819_PlayRogerMDC(); - SYSTEM_DelayMs(100); - - BK4819_ExitTxMute(); - - MSG_FSKSendData(); - - SYSTEM_DelayMs(100); - - - APP_EndTransmission(); - // this must be run after end of TX, otherwise radio will still TX transmit without even RED LED on - FUNCTION_Select(FUNCTION_FOREGROUND); - - RADIO_SetVfoState(VFO_STATE_NORMAL); - - BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false); - - MSG_EnableRX(true); - if (!bServiceMessage) { - moveUP(rxMessage); - sprintf(rxMessage[3], "> %s", txMessage); - memset(lastcMessage, 0, sizeof(lastcMessage)); - memcpy(lastcMessage, txMessage, TX_MSG_LENGTH); - cIndex = 0; - prevKey = 0; - prevLetter = 0; - memset(cMessage, 0, sizeof(cMessage)); - } - msgStatus = READY; - - } else { - AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL); - } -} - uint8_t validate_char( uint8_t rchar ) { if ( (rchar == 0x1b) || (rchar >= 32 && rchar <= 127) ) { return rchar; @@ -741,8 +676,10 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { else { char dencryptedTxMessage[TX_MSG_LENGTH]; + // memset(dencryptedTxMessage,0,sizeof(dencryptedTxMessage)); - CRYPTO_Crypt(dataPacket.encrypted.ciphertext, TX_MSG_LENGTH, dencryptedTxMessage, nonce, key, 256); + if(dataPacket.unencrypted.header == ENCRYPTED_MESSAGE_PACKET) + CRYPTO_Crypt(dataPacket.encrypted.ciphertext, TX_MSG_LENGTH, dencryptedTxMessage, nonce, key, 256); snprintf(rxMessage[3], TX_MSG_LENGTH + 2, "< %s", dencryptedTxMessage); } @@ -848,7 +785,7 @@ void processBackspace() { void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { uint8_t state = bKeyPressed + 2 * bKeyHeld; - + if (state == MSG_BUTTON_EVENT_SHORT) { switch (Key) @@ -885,7 +822,6 @@ void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { break;*/ case KEY_MENU: // Send message - // MSG_Send(cMessage, false); memset(dataPacket.serializedArray,0,sizeof(dataPacket.serializedArray)); dataPacket.unencrypted.header=ENCRYPTED_MESSAGE_PACKET; memcpy(dataPacket.unencrypted.payload, cMessage, sizeof(dataPacket.unencrypted.payload)); diff --git a/app/messenger.h b/app/messenger.h index a88b87bdb..c6567f8ea 100644 --- a/app/messenger.h +++ b/app/messenger.h @@ -72,8 +72,8 @@ void MSG_EnableRX(const bool enable); void MSG_StorePacket(const uint16_t interrupt_bits); void MSG_Init(); void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld); -void MSG_Send(char txMessage[TX_MSG_LENGTH], bool bServiceMessage); void MSG_SendPacket(union DataPacket packet); +void MSG_FSKSendData(); #endif From 08e9e62df703335e60687e459723dcc142cafa4c Mon Sep 17 00:00:00 2001 From: Nunu Date: Tue, 23 Jan 2024 18:00:03 +0100 Subject: [PATCH 07/39] refactor/cleanup. --- app/messenger.c | 35 +++++++++++++++++------------------ app/messenger.h | 22 +++++++--------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index c2c3b2f8d..5ce763b67 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -28,7 +28,7 @@ const uint8_t MSG_BUTTON_STATE_HELD = 1 << 1; const uint8_t MSG_BUTTON_EVENT_SHORT = 0; const uint8_t MSG_BUTTON_EVENT_LONG = MSG_BUTTON_STATE_HELD; -const uint8_t MAX_MSG_LENGTH = TX_MSG_LENGTH - 1; +const uint8_t MAX_MSG_LENGTH = PAYLOAD_LENGTH - 1; const uint16_t TONE2_FREQ = 0x3065; // 0x2854 @@ -41,9 +41,9 @@ unsigned char numberOfLettersAssignedToKey[9] = { 4, 3, 3, 3, 3, 3, 4, 3, 4 }; char T9TableNum[9][4] = { {'1', '\0', '\0', '\0'}, {'2', '\0', '\0', '\0'}, {'3', '\0', '\0', '\0'}, {'4', '\0', '\0', '\0'}, {'5', '\0', '\0', '\0'}, {'6', '\0', '\0', '\0'}, {'7', '\0', '\0', '\0'}, {'8', '\0', '\0', '\0'}, {'9', '\0', '\0', '\0'} }; unsigned char numberOfNumsAssignedToKey[9] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; -char cMessage[TX_MSG_LENGTH]; -char lastcMessage[TX_MSG_LENGTH]; -char rxMessage[4][MAX_RX_MSG_LENGTH + 2]; +char cMessage[PAYLOAD_LENGTH]; +char lastcMessage[PAYLOAD_LENGTH]; +char rxMessage[4][PAYLOAD_LENGTH + 2]; unsigned char cIndex = 0; unsigned char prevKey = 0, prevLetter = 0; KeyboardType keyboardType = UPPERCASE; @@ -234,7 +234,7 @@ void MSG_FSKSendData() { (0u << 0); // 0 ~ 7 ??? // Set packet length (not including pre-amble and sync bytes that we can't seem to disable) - BK4819_WriteRegister(BK4819_REG_5D, ((MSG_HEADER_LENGTH + MAX_RX_MSG_LENGTH) << 8)); + BK4819_WriteRegister(BK4819_REG_5D, ((sizeof(dataPacket.serializedArray)) << 8)); // REG_5A // @@ -279,6 +279,9 @@ void MSG_FSKSendData() { } } + // clear dataPacket + memset(dataPacket.serializedArray, 0, sizeof(dataPacket.serializedArray));; + // enable FSK TX BK4819_WriteRegister(BK4819_REG_59, (1u << 11) | fsk_reg59); @@ -514,7 +517,7 @@ void MSG_EnableRX(const bool enable) { { // packet size .. sync + 14 bytes - size of a single packet - uint16_t size = (MSG_HEADER_LENGTH + MAX_RX_MSG_LENGTH); + uint16_t size = sizeof(dataPacket.serializedArray); // size -= (fsk_reg59 & (1u << 3)) ? 4 : 2; size = (((size + 1) / 2) * 2) + 2; // round up to even, else FSK RX doesn't work BK4819_WriteRegister(BK4819_REG_5D, (size << 8)); @@ -535,7 +538,7 @@ void MSG_EnableRX(const bool enable) { // ----------------------------------------------------- -void moveUP(char (*rxMessages)[MAX_RX_MSG_LENGTH + 2]) { +void moveUP(char (*rxMessages)[PAYLOAD_LENGTH + 2]) { // Shift existing lines up strcpy(rxMessages[0], rxMessages[1]); strcpy(rxMessages[1], rxMessages[2]); @@ -562,11 +565,7 @@ void MSG_SendPacket(union DataPacket packet) { dataPacket = packet; if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ - // char encryptedTxMessage[TX_MSG_LENGTH]; - - // memset(encryptedTxMessage, 0, sizeof(encryptedTxMessage)); - - CRYPTO_Crypt(packet.encrypted.ciphertext, TX_MSG_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); + CRYPTO_Crypt(packet.encrypted.ciphertext, PAYLOAD_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); } BK4819_DisableDTMF(); @@ -597,7 +596,7 @@ void MSG_SendPacket(union DataPacket packet) { moveUP(rxMessage); sprintf(rxMessage[3], "> %s", packet.encrypted.ciphertext); memset(lastcMessage, 0, sizeof(lastcMessage)); - memcpy(lastcMessage, packet.encrypted.ciphertext, TX_MSG_LENGTH); + memcpy(lastcMessage, packet.encrypted.ciphertext, PAYLOAD_LENGTH); cIndex = 0; prevKey = 0; prevLetter = 0; @@ -671,17 +670,17 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { } else { moveUP(rxMessage); if (dataPacket.unencrypted.header >= INVALID_PACKET) { - snprintf(rxMessage[3], TX_MSG_LENGTH + 2, "? unknown msg format!"); + snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "ERROR: INVALID PACKET."); } else { - char dencryptedTxMessage[TX_MSG_LENGTH]; + char dencryptedTxMessage[PAYLOAD_LENGTH]; // memset(dencryptedTxMessage,0,sizeof(dencryptedTxMessage)); if(dataPacket.unencrypted.header == ENCRYPTED_MESSAGE_PACKET) - CRYPTO_Crypt(dataPacket.encrypted.ciphertext, TX_MSG_LENGTH, dencryptedTxMessage, nonce, key, 256); + CRYPTO_Crypt(dataPacket.encrypted.ciphertext, PAYLOAD_LENGTH, dencryptedTxMessage, nonce, key, 256); - snprintf(rxMessage[3], TX_MSG_LENGTH + 2, "< %s", dencryptedTxMessage); + snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dencryptedTxMessage); } #ifdef ENABLE_MESSENGER_UART @@ -815,7 +814,7 @@ void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { break; case KEY_UP: memset(cMessage, 0, sizeof(cMessage)); - memcpy(cMessage, lastcMessage, TX_MSG_LENGTH); + memcpy(cMessage, lastcMessage, PAYLOAD_LENGTH); cIndex = strlen(cMessage); break; /*case KEY_DOWN: diff --git a/app/messenger.h b/app/messenger.h index c6567f8ea..cf3b69f39 100644 --- a/app/messenger.h +++ b/app/messenger.h @@ -8,6 +8,11 @@ #include #include "driver/keyboard.h" +enum { + NONCE_LENGTH = 5, + PAYLOAD_LENGTH = 20 +}; + typedef enum KeyboardType { UPPERCASE, LOWERCASE, @@ -15,18 +20,10 @@ typedef enum KeyboardType { END_TYPE_KBRD } KeyboardType; -enum { - TX_MSG_LENGTH = 30, - MSG_HEADER_LENGTH = 20, - MAX_RX_MSG_LENGTH = TX_MSG_LENGTH + 2 -}; -//const uint8_t TX_MSG_LENGTH = 30; -//const uint8_t MAX_RX_MSG_LENGTH = TX_MSG_LENGTH + 2; - extern KeyboardType keyboardType; extern uint16_t gErrorsDuringMSG; -extern char cMessage[TX_MSG_LENGTH]; -extern char rxMessage[4][MAX_RX_MSG_LENGTH + 2]; +extern char cMessage[PAYLOAD_LENGTH]; +extern char rxMessage[4][PAYLOAD_LENGTH + 2]; extern uint8_t hasNewMessage; extern uint8_t keyTickCounter; @@ -43,11 +40,6 @@ typedef enum PacketType { INVALID_PACKET } PacketType; -enum { - NONCE_LENGTH = 10, - PAYLOAD_LENGTH = 19 -}; - // Data Packet definition // 2024 kamilsss655 union DataPacket { From 50f2e1dbaabae4e8100cbae60ae17522c2c3afc2 Mon Sep 17 00:00:00 2001 From: Nunu Date: Tue, 23 Jan 2024 18:50:34 +0100 Subject: [PATCH 08/39] send and receive nonce --- Makefile | 2 +- app/messenger.c | 8 +++++++- app/messenger.h | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 1d99e32e4..38d9c5fc3 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ENABLE_LTO := 1 # ---- STOCK QUANSHENG FERATURES ---- ENABLE_UART := 1 ENABLE_AIRCOPY := 0 -ENABLE_FMRADIO := 1 +ENABLE_FMRADIO := 0 ENABLE_NOAA := 0 ENABLE_VOICE := 0 ENABLE_VOX := 1 diff --git a/app/messenger.c b/app/messenger.c index 5ce763b67..73ded61c1 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -566,6 +566,7 @@ void MSG_SendPacket(union DataPacket packet) { if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ CRYPTO_Crypt(packet.encrypted.ciphertext, PAYLOAD_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); + memcpy(dataPacket.encrypted.nonce, nonce, sizeof(dataPacket.encrypted.nonce)); } BK4819_DisableDTMF(); @@ -678,7 +679,12 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { // memset(dencryptedTxMessage,0,sizeof(dencryptedTxMessage)); if(dataPacket.unencrypted.header == ENCRYPTED_MESSAGE_PACKET) - CRYPTO_Crypt(dataPacket.encrypted.ciphertext, PAYLOAD_LENGTH, dencryptedTxMessage, nonce, key, 256); + CRYPTO_Crypt(dataPacket.encrypted.ciphertext, + PAYLOAD_LENGTH, + dencryptedTxMessage, + dataPacket.encrypted.nonce, + key, + 256); snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dencryptedTxMessage); } diff --git a/app/messenger.h b/app/messenger.h index cf3b69f39..59c27d3e3 100644 --- a/app/messenger.h +++ b/app/messenger.h @@ -9,8 +9,8 @@ #include "driver/keyboard.h" enum { - NONCE_LENGTH = 5, - PAYLOAD_LENGTH = 20 + NONCE_LENGTH = 13, + PAYLOAD_LENGTH = 30 }; typedef enum KeyboardType { @@ -55,7 +55,7 @@ union DataPacket char payload[PAYLOAD_LENGTH]; // uint8_t signature[SIGNATURE_LENGTH]; } unencrypted; - // header + payload + nonce + // header + payload + nonce = must be an even number uint8_t serializedArray[1+PAYLOAD_LENGTH+NONCE_LENGTH]; }; From 4f6a872a726831fb4e26b967b5143eedf7c65884 Mon Sep 17 00:00:00 2001 From: Nunu Date: Tue, 23 Jan 2024 21:01:10 +0100 Subject: [PATCH 09/39] random number generation --- helper/crypto.c | 23 +++++++++++++++++++++++ helper/crypto.h | 6 +++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/helper/crypto.c b/helper/crypto.c index 6904a9756..7ff421d7a 100644 --- a/helper/crypto.c +++ b/helper/crypto.c @@ -16,6 +16,8 @@ #include "crypto.h" #include "external/chacha/chacha.h" +#include "driver/bk4819.h" +#include "driver/systick.h" // Used for both encryption and decryption void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const void *key, int key_len) @@ -39,4 +41,25 @@ void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const v } } +// Generate random byte +uint8_t CRYPTO_RandomByte() +{ + uint8_t randByte = 0x00; + uint8_t noise; + for(uint8_t i = 0; i < 8; i++) { + noise = BK4819_ReadRegister(BK4819_REG_65) & 0x007F; + randByte |= (noise & 0x01)<< i; + SYSTICK_DelayUs(979); + } + return randByte; +} + +// Generate random number from the radio noise +void CRYPTO_Random(void *output, int len) +{ + for (uint8_t i = 0; i < len; i++) { + ((unsigned char *)output)[i] = CRYPTO_RandomByte(); + } +} + \ No newline at end of file diff --git a/helper/crypto.h b/helper/crypto.h index 748e0fe9f..085322f5d 100644 --- a/helper/crypto.h +++ b/helper/crypto.h @@ -14,5 +14,9 @@ * limitations under the License. */ +#include + // Used for both encryption and decryption -void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const void *key, int key_len); \ No newline at end of file +void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const void *key, int key_len); +void CRYPTO_Random(void *output, int len); +uint8_t CRYPTO_RandomByte(); \ No newline at end of file From 0b3785b64454499b2bd56afb7c3a214a2f8bad93 Mon Sep 17 00:00:00 2001 From: Nunu Date: Wed, 24 Jan 2024 10:39:23 +0100 Subject: [PATCH 10/39] nonce working --- app/messenger.c | 22 +++++++++++++++------- app/messenger.h | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index 73ded61c1..ad5fc49c6 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -63,11 +63,6 @@ unsigned char key[32] = { 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f }; -unsigned char nonce[12] = { - 0x07, 0x00, 0x00, 0x00, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 -}; - // ----------------------------------------------------- void MSG_FSKSendData() { @@ -565,7 +560,20 @@ void MSG_SendPacket(union DataPacket packet) { dataPacket = packet; if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ - CRYPTO_Crypt(packet.encrypted.ciphertext, PAYLOAD_LENGTH, dataPacket.encrypted.ciphertext, nonce, key, 256); + char nonce[NONCE_LENGTH]; + CRYPTO_Random(nonce, NONCE_LENGTH-1); + // this is wat happens when we have global state + memcpy(packet.encrypted.nonce, nonce, NONCE_LENGTH); + + CRYPTO_Crypt( + packet.encrypted.ciphertext, + PAYLOAD_LENGTH, + dataPacket.encrypted.ciphertext, + &packet.encrypted.nonce, + key, + 256 + ); + memcpy(dataPacket.encrypted.nonce, nonce, sizeof(dataPacket.encrypted.nonce)); } @@ -682,7 +690,7 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { CRYPTO_Crypt(dataPacket.encrypted.ciphertext, PAYLOAD_LENGTH, dencryptedTxMessage, - dataPacket.encrypted.nonce, + &dataPacket.encrypted.nonce, key, 256); diff --git a/app/messenger.h b/app/messenger.h index 59c27d3e3..f2ceb24dc 100644 --- a/app/messenger.h +++ b/app/messenger.h @@ -46,7 +46,7 @@ union DataPacket struct{ uint8_t header; uint8_t ciphertext[PAYLOAD_LENGTH]; - uint8_t nonce[NONCE_LENGTH]; + unsigned char nonce[NONCE_LENGTH]; // uint8_t signature[SIGNATURE_LENGTH]; } encrypted; From 155fbc5da0e50694880a7a869f84b45aed2f849c Mon Sep 17 00:00:00 2001 From: Nunu Date: Wed, 24 Jan 2024 10:56:51 +0100 Subject: [PATCH 11/39] generate full nonce --- app/messenger.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/messenger.c b/app/messenger.c index ad5fc49c6..311ddbb6b 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -561,7 +561,8 @@ void MSG_SendPacket(union DataPacket packet) { if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ char nonce[NONCE_LENGTH]; - CRYPTO_Random(nonce, NONCE_LENGTH-1); + + CRYPTO_Random(nonce, NONCE_LENGTH); // this is wat happens when we have global state memcpy(packet.encrypted.nonce, nonce, NONCE_LENGTH); From 0f60145b0708bd3d336bab39878905c2aacbea52 Mon Sep 17 00:00:00 2001 From: Nunu Date: Wed, 24 Jan 2024 15:43:15 +0100 Subject: [PATCH 12/39] fix encryption = 0 compilation condition --- app/messenger.c | 67 ++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index 311ddbb6b..974d6c149 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -558,25 +558,26 @@ void MSG_SendPacket(union DataPacket packet) { // later refactor to not use global state but pass dataPacket type everywhere dataPacket = packet; - - if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ - char nonce[NONCE_LENGTH]; - - CRYPTO_Random(nonce, NONCE_LENGTH); - // this is wat happens when we have global state - memcpy(packet.encrypted.nonce, nonce, NONCE_LENGTH); - - CRYPTO_Crypt( - packet.encrypted.ciphertext, - PAYLOAD_LENGTH, - dataPacket.encrypted.ciphertext, - &packet.encrypted.nonce, - key, - 256 - ); - - memcpy(dataPacket.encrypted.nonce, nonce, sizeof(dataPacket.encrypted.nonce)); - } + #ifdef ENABLE_ENCRYPTION + if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ + char nonce[NONCE_LENGTH]; + + CRYPTO_Random(nonce, NONCE_LENGTH); + // this is wat happens when we have global state + memcpy(packet.encrypted.nonce, nonce, NONCE_LENGTH); + + CRYPTO_Crypt( + packet.encrypted.ciphertext, + PAYLOAD_LENGTH, + dataPacket.encrypted.ciphertext, + &packet.encrypted.nonce, + key, + 256 + ); + + memcpy(dataPacket.encrypted.nonce, nonce, sizeof(dataPacket.encrypted.nonce)); + } + #endif BK4819_DisableDTMF(); @@ -684,18 +685,22 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { } else { - char dencryptedTxMessage[PAYLOAD_LENGTH]; - // memset(dencryptedTxMessage,0,sizeof(dencryptedTxMessage)); - - if(dataPacket.unencrypted.header == ENCRYPTED_MESSAGE_PACKET) - CRYPTO_Crypt(dataPacket.encrypted.ciphertext, - PAYLOAD_LENGTH, - dencryptedTxMessage, - &dataPacket.encrypted.nonce, - key, - 256); - - snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dencryptedTxMessage); + #ifdef ENABLE_ENCRYPTION + char dencryptedTxMessage[PAYLOAD_LENGTH]; + // memset(dencryptedTxMessage,0,sizeof(dencryptedTxMessage)); + + if(dataPacket.unencrypted.header == ENCRYPTED_MESSAGE_PACKET) + CRYPTO_Crypt(dataPacket.encrypted.ciphertext, + PAYLOAD_LENGTH, + dencryptedTxMessage, + &dataPacket.encrypted.nonce, + key, + 256); + + snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dencryptedTxMessage); + #else + snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dataPacket.unencrypted.payload); + #endif } #ifdef ENABLE_MESSENGER_UART From 4fbdcbb352adfc9a70ea53268ca6ec93c0bf702a Mon Sep 17 00:00:00 2001 From: Nunu Date: Wed, 24 Jan 2024 15:45:39 +0100 Subject: [PATCH 13/39] menu enc key added --- app/menu.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++------ settings.h | 3 +++ ui/menu.c | 23 ++++++++++++++++++++ ui/menu.h | 3 +++ 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/app/menu.c b/app/menu.c index b20f810c1..a80e06975 100644 --- a/app/menu.c +++ b/app/menu.c @@ -1183,7 +1183,13 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; - if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0) + if (edit_index >= 0 && ( + UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME + #ifdef ENABLE_ENCRYPTION + || UI_MENU_GetCurrentMenuId() == MENU_ENC_KEY + #endif + )) + { // currently editing the channel name if (edit_index < 10) @@ -1455,15 +1461,42 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) return; } + #ifdef ENABLE_ENCRYPTION + if (UI_MENU_GetCurrentMenuId() == MENU_ENC_KEY) + { + if (edit_index < 0) + { // enter encryption key edit mode + // pad the encryption key out with '_' + // TODO: Extract to shared function - common with MENU_MEM_NAME below + edit_index = strlen(edit); + while (edit_index < 10) + edit[edit_index++] = '_'; + edit[edit_index] = 0; + edit_index = 0; // 'edit_index' is going to be used as the cursor position + + return; + } + else if (edit_index >= 0 && edit_index < 10) + { // editing the encryption key characters + + if (++edit_index < 10) + return; // next char + + // exit, save encryption key + memcpy(gEeprom.ENC_KEY, edit, sizeof(gEeprom.ENC_KEY)); + } + } + #endif + if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) { if (edit_index < 0) { // enter channel name edit mode if (!RADIO_CheckValidChannel(gSubMenuSelection, false, 0)) - return; - + return; + SETTINGS_FetchChannelName(edit, gSubMenuSelection); - + // pad the channel name out with '_' edit_index = strlen(edit); while (edit_index < 10) @@ -1613,10 +1646,19 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) uint8_t Channel; bool bCheckScanList; - if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && gIsInSubMenu && edit_index >= 0) + if (gIsInSubMenu && + edit_index >= 0 && + ( + UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME + #ifdef ENABLE_ENCRYPTION + || UI_MENU_GetCurrentMenuId() == MENU_ENC_KEY + #endif + ) + ) { // change the character if (bKeyPressed && edit_index < 10 && Direction != 0) { + // TODO: Allow special chars when setting encryption key const char unwanted[] = "$%&!\"':;?^`|{}"; char c = edit[edit_index] + Direction; unsigned int i = 0; @@ -1747,8 +1789,16 @@ void MENU_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) MENU_Key_STAR(bKeyPressed, bKeyHeld); break; case KEY_F: - if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0) - { // currently editing the channel name + if (edit_index >= 0 && + ( + UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME + #ifdef ENABLE_ENCRYPTION + || UI_MENU_GetCurrentMenuId() == MENU_ENC_KEY + #endif + ) + ) + { // adds space, + // currently editing the channel name or enc_key if (!bKeyHeld && bKeyPressed) { gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; diff --git a/settings.h b/settings.h index 5f3f83c39..377c06dc3 100644 --- a/settings.h +++ b/settings.h @@ -245,6 +245,9 @@ typedef struct { #ifdef ENABLE_PWRON_PASSWORD uint32_t POWER_ON_PASSWORD; uint8_t PASSWORD_WRONG_ATTEMPTS; +#endif +#ifdef ENABLE_ENCRYPTION + char ENC_KEY[10]; #endif uint16_t VOX1_THRESHOLD; uint16_t VOX0_THRESHOLD; diff --git a/ui/menu.c b/ui/menu.c index e81cb1357..79009d148 100644 --- a/ui/menu.c +++ b/ui/menu.c @@ -111,6 +111,9 @@ const t_menu_item MenuList[] = {"RxMode", VOICE_ID_DUAL_STANDBY, MENU_TDR }, #ifdef ENABLE_PWRON_PASSWORD {"Passwd", VOICE_ID_INVALID, MENU_PASSWORD }, // power on password +#endif +#ifdef ENABLE_ENCRYPTION + {"EncKey", VOICE_ID_INVALID, MENU_ENC_KEY }, // encryption key #endif {"Sql", VOICE_ID_SQUELCH, MENU_SQL }, // hidden menu items from here on @@ -721,6 +724,26 @@ void UI_DisplayMenu(void) already_printed = true; break; } + #ifdef ENABLE_ENCRYPTION + case MENU_ENC_KEY: + { + if (!gIsInSubMenu) + { // show the key + strcpy(String, "****"); + UI_PrintString(String, menu_item_x1, menu_item_x2, 2, 8); + } + else + { // show the key being edited + sprintf(String, "%s", gEeprom.ENC_KEY); + UI_PrintString(edit, (menu_item_x1 -2), 0, 2, 8); + if (edit_index != -1 && edit_index < 10) + UI_PrintString( "^", (menu_item_x1 -2) + (8 * edit_index), 0, 4, 8); // show the cursor + } + + already_printed = true; + break; + } + #endif case MENU_SAVE: strcpy(String, gSubMenu_SAVE[gSubMenuSelection]); diff --git a/ui/menu.h b/ui/menu.h index 74bf7a2d2..f40cace2b 100644 --- a/ui/menu.h +++ b/ui/menu.h @@ -61,6 +61,9 @@ enum MENU_TDR, #ifdef ENABLE_PWRON_PASSWORD MENU_PASSWORD, +#endif +#ifdef ENABLE_ENCRYPTION + MENU_ENC_KEY, #endif MENU_BEEP, #ifdef ENABLE_VOICE From 0f9907992048f474b765d996a07bd6db7fac7088 Mon Sep 17 00:00:00 2001 From: Nunu Date: Wed, 24 Jan 2024 16:19:56 +0100 Subject: [PATCH 14/39] update comment --- board.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board.c b/board.c index 7f249ed46..3a0f55799 100644 --- a/board.c +++ b/board.c @@ -855,7 +855,7 @@ void BOARD_FactoryReset(bool bIsAll) { if ( !(i >= 0x0EE0 && i < 0x0F18) && // ANI ID + DTMF codes - !(i >= 0x0F30 && i < 0x0F50) && // AES KEY + F LOCK + Scramble Enable + !(i >= 0x0F30 && i < 0x0F50) && // ENCRYPTION KEY + F LOCK + Scramble Enable !(i >= 0x1C00 && i < 0x1E00) && // DTMF contacts !(i >= 0x0EB0 && i < 0x0ED0) && // Welcome strings !(i >= 0x0EA0 && i < 0x0EA8) && // Voice Prompt From f2c6f8930c528d52230a6cc68f27ac79fc2b9268 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 11:37:08 +0100 Subject: [PATCH 15/39] fix: docker build script auto prune old images --- compile-with-docker.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compile-with-docker.sh b/compile-with-docker.sh index 43c480a5c..fe2c0a58c 100755 --- a/compile-with-docker.sh +++ b/compile-with-docker.sh @@ -1,3 +1,5 @@ #!/bin/sh +# first clean images older than 24h, you will run out of disk space one day +docker image prune -a --force --filter "until=24h" docker build -t uvk5 . docker run --rm -v ${PWD}/compiled-firmware:/app/compiled-firmware uvk5 /bin/bash -c "cd /app && make clean && make && cp firmware* compiled-firmware/" From a6754c68a68513917a1c4781f64aa94e6dccf765 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 12:04:52 +0100 Subject: [PATCH 16/39] working EncKey menu, saving and loading --- app/app.c | 1 + app/menu.c | 17 +++++++++++++++-- board.c | 13 ++----------- docs/UV K5 EEPROM.xlsx | Bin 68502 -> 68514 bytes settings.c | 13 +++++++++++++ settings.h | 5 ++++- ui/menu.c | 22 ++++++++++++++++------ 7 files changed, 51 insertions(+), 20 deletions(-) diff --git a/app/app.c b/app/app.c index d68707cb0..38fa341dd 100644 --- a/app/app.c +++ b/app/app.c @@ -1963,6 +1963,7 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) SETTINGS_SaveSettings(); else gFlagSaveSettings = 1; + gRequestSaveSettings = false; gUpdateStatus = true; } diff --git a/app/menu.c b/app/menu.c index a80e06975..a77bf5eff 100644 --- a/app/menu.c +++ b/app/menu.c @@ -485,6 +485,13 @@ void MENU_AcceptSetting(void) break; #endif + #ifdef ENABLE_ENCRYPTION + case MENU_ENC_KEY: + memmove(gEeprom.ENC_KEY, edit, sizeof(gEeprom.ENC_KEY)); + gUpdateStatus = true; + break; + #endif + case MENU_W_N: gTxVfo->CHANNEL_BANDWIDTH = gSubMenuSelection; gRequestSaveChannel = 1; @@ -943,6 +950,10 @@ void MENU_ShowCurrentSetting(void) gSubMenuSelection = gEeprom.MrChannel[gEeprom.TX_VFO]; break; + // case MENU_ENC_KEY: + // gSubMenuSelection = 1; + // break; + case MENU_SAVE: gSubMenuSelection = gEeprom.BATTERY_SAVE; break; @@ -1446,7 +1457,7 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) #if 1 if (UI_MENU_GetCurrentMenuId() == MENU_DEL_CH || UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) if (!RADIO_CheckValidChannel(gSubMenuSelection, false, 0)) - return; // invalid channel + return; // invalid channel #endif gAskForConfirmation = 0; @@ -1483,7 +1494,6 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) return; // next char // exit, save encryption key - memcpy(gEeprom.ENC_KEY, edit, sizeof(gEeprom.ENC_KEY)); } } #endif @@ -1538,6 +1548,9 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) if (UI_MENU_GetCurrentMenuId() == MENU_RESET || UI_MENU_GetCurrentMenuId() == MENU_MEM_CH || UI_MENU_GetCurrentMenuId() == MENU_DEL_CH || + #ifdef ENABLE_ENCRYPTION + UI_MENU_GetCurrentMenuId() == MENU_ENC_KEY || + #endif UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) { switch (gAskForConfirmation) diff --git a/board.c b/board.c index 3a0f55799..2e5017fb1 100644 --- a/board.c +++ b/board.c @@ -731,17 +731,8 @@ void BOARD_EEPROM_Init(void) } } - // 0F30..0F3F - EEPROM_ReadBuffer(0x0F30, gCustomAesKey, sizeof(gCustomAesKey)); - bHasCustomAesKey = false; - for (i = 0; i < ARRAY_SIZE(gCustomAesKey); i++) - { - if (gCustomAesKey[i] != 0xFFFFFFFFu) - { - bHasCustomAesKey = true; - return; - } - } + // 0F30..0F3F - load encryption key + EEPROM_ReadBuffer(0x0F30, gEeprom.ENC_KEY, sizeof(gEeprom.ENC_KEY)); #ifdef ENABLE_SPECTRUM_SHOW_CHANNEL_NAME BOARD_gMR_LoadChannels(); diff --git a/docs/UV K5 EEPROM.xlsx b/docs/UV K5 EEPROM.xlsx index a722c21cbadacf46ec600a96232cf59f19dd3910..9e1ecf34e6d00961585d675a1a436bb06998f0fb 100644 GIT binary patch literal 68514 zcmdSC2|SeT_dlMLB+_CjiIkEGiIR0%g)D_OYuRNPS;sn+P)TS*Le_aGsgT`-kdS4_ zzGdH+!5Cv^e%C$qc^c1S`kMZq-|zeSKd&11ocliaIoCPw>zs3)>(X=XEaNJU70k@c zD->OiUS2`<-#Tz@Xm4umASMcZCO;}rFFMHTJMMmeAMSA5G3Mxm995+p0ZG1(g0|bz z@5O%do|-+ic@t~E`=W<8ypSZL_;yv>1MLU*oHe+2;mW<0d3%gKQJ>05H(bSzRK|Ff zY&?2K6Mk}^eV~TQbVb+kFLCaUK`JZ*Shce?4E@FmW|eo0KUu$( z^`Mg4#t?zNvCmwXjE4fmC*^~op_{X_`t0qDrkN^pH{CgC?&z+z%Tq~pEQ)ixgrSPR zPUJ#)-{x~?*D(7$mwY}3jPP~kiWTQ*nu#4efv^^Hf!kXd!Qob-2pj7Ftrq*{gBv`@ zFCnJa=i3P%3}roxcof#YC$22jI_2H{1FmtqkI4{mZ%kh#U=*eq;_?PYn%cU1E3=-p zOKjPncK^yz*3dQk8hp+XLOtD;)=2bS=}GF;+<&)e^?{r#dAgBl(k43ma&9Ts75hG! zbrFuFyzF!SYuh;v;q*l5N0zhsHmnnc7gt<93uC#RXy_1q+3)Q^vUha3X3yIi{5vL} z?ROck=EA3do#rf&o zg!R&zXY^G1IC%wLtm#(hWoHg}qHOxPNrrFri;;1JmCPqyn@|21Klh4C>3j)|mNs~` z@jM3}y)(!89J^d<9(F-cJ6c0LmQ%mIRby;kkma?-EliDdE5Y{o$o>aS2VR`1I2qZN z;eEeJ@TGbDK`#A`euasT&0o4DpJv0uW$a~#H8iqrWrtil`}Awh?R~NlJA5L2IKu=> z%mnsc9lQToVD@00tvZ&Q*>`HQr}ilUYw4|fVS~4Ke7oZufEMk)4nMY2=uy!Q=0Yy+ zuX8mqvMHL6o{u5Rl#|4PI!&TL|=t6VH4%E7xoGI*MIzYEu!4 z7%N~n`I=|jR+QTO@NmJMW8d0aDxJq4ah+RLF&B=X+p=w!kXQQtMC?}o=XM|Lgv2}= z3Jы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 diff --git a/settings.c b/settings.c index 1bae89f0d..63ddffb22 100644 --- a/settings.c +++ b/settings.c @@ -176,6 +176,10 @@ void SETTINGS_SaveSettings(void) memcpy(&State[0], &gEeprom.FM_FrequencyPlaying, 2); EEPROM_WriteBuffer(0x0E88, State, true); #endif + + #ifdef ENABLE_ENCRYPTION + SETTINGS_SaveEncryptionKey(); + #endif } void SETTINGS_SaveChannel(uint8_t Channel, uint8_t VFO, const VFO_Info_t *pVFO, uint8_t Mode) @@ -264,6 +268,15 @@ void SETTINGS_SaveChannelName(uint8_t channel, const char * name) EEPROM_WriteBuffer(0x0F58 + offset, buf + 8, true); } +#ifdef ENABLE_ENCRYPTION +void SETTINGS_SaveEncryptionKey() +{ + // TODO: this should probably autoadjust to sizeof(gEeprom.ENC_KEY) + EEPROM_WriteBuffer(0x0F30, &gEeprom.ENC_KEY, true); + EEPROM_WriteBuffer(0x0F38, &gEeprom.ENC_KEY + 8, true); +} +#endif + void SETTINGS_FetchChannelName(char *s, const int channel) { int i; diff --git a/settings.h b/settings.h index 377c06dc3..38e7540f2 100644 --- a/settings.h +++ b/settings.h @@ -247,7 +247,7 @@ typedef struct { uint8_t PASSWORD_WRONG_ATTEMPTS; #endif #ifdef ENABLE_ENCRYPTION - char ENC_KEY[10]; + char ENC_KEY[16]; #endif uint16_t VOX1_THRESHOLD; uint16_t VOX0_THRESHOLD; @@ -282,4 +282,7 @@ void SETTINGS_FetchChannelName(char *s, const int channel); void SETTINGS_SaveBatteryCalibration(const uint16_t * batteryCalibration); void SETTINGS_UpdateChannel(uint8_t channel, const VFO_Info_t *pVFO, bool keep); void SETTINGS_SetVfoFrequency(uint32_t frequency); +#ifdef ENABLE_ENCRYPTION + void SETTINGS_SaveEncryptionKey(); +#endif #endif diff --git a/ui/menu.c b/ui/menu.c index 79009d148..9b29628cc 100644 --- a/ui/menu.c +++ b/ui/menu.c @@ -728,16 +728,23 @@ void UI_DisplayMenu(void) case MENU_ENC_KEY: { if (!gIsInSubMenu) - { // show the key - strcpy(String, "****"); + { // show placeholder in main menu + // strcpy(String, "****"); + sprintf(String, "%s", gEeprom.ENC_KEY); UI_PrintString(String, menu_item_x1, menu_item_x2, 2, 8); } else { // show the key being edited - sprintf(String, "%s", gEeprom.ENC_KEY); - UI_PrintString(edit, (menu_item_x1 -2), 0, 2, 8); - if (edit_index != -1 && edit_index < 10) - UI_PrintString( "^", (menu_item_x1 -2) + (8 * edit_index), 0, 4, 8); // show the cursor + if (edit_index != -1 || gAskForConfirmation) { + UI_PrintString(edit, (menu_item_x1 -2), 0, 2, 8); + // show the cursor + if(edit_index < 10) + UI_PrintString( "^", (menu_item_x1 -2) + (8 * edit_index), 0, 4, 8); + } + else{ + strcpy(String, "hash"); + UI_PrintString(String, (menu_item_x1 -2), 0, 2, 8); + } } already_printed = true; @@ -980,6 +987,9 @@ void UI_DisplayMenu(void) if ((UI_MENU_GetCurrentMenuId() == MENU_RESET || UI_MENU_GetCurrentMenuId() == MENU_MEM_CH || + #ifdef ENABLE_ENCRYPTION + UI_MENU_GetCurrentMenuId() == MENU_ENC_KEY || + #endif UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME || UI_MENU_GetCurrentMenuId() == MENU_DEL_CH) && gAskForConfirmation) { // display confirmation From c6ad3d78049259ac0b198ff87bff0fa2ab211ccb Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 12:36:03 +0100 Subject: [PATCH 17/39] fix updating enckey from menu --- app/menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/menu.c b/app/menu.c index a77bf5eff..2bf2ddf44 100644 --- a/app/menu.c +++ b/app/menu.c @@ -487,7 +487,7 @@ void MENU_AcceptSetting(void) #ifdef ENABLE_ENCRYPTION case MENU_ENC_KEY: - memmove(gEeprom.ENC_KEY, edit, sizeof(gEeprom.ENC_KEY)); + memmove(gEeprom.ENC_KEY, edit, sizeof(edit)); gUpdateStatus = true; break; #endif From b04baaa701bcb0bd9ca8aa2e6010792ec0a00b06 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 13:19:17 +0100 Subject: [PATCH 18/39] eeprom key works --- app/messenger.c | 12 ++++++------ settings.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index 974d6c149..309cc5309 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -58,10 +58,10 @@ uint8_t hasNewMessage = 0; uint8_t keyTickCounter = 0; -unsigned char key[32] = { - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f -}; +// unsigned char key[32] = { +// 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, +// 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f +// }; // ----------------------------------------------------- @@ -571,7 +571,7 @@ void MSG_SendPacket(union DataPacket packet) { PAYLOAD_LENGTH, dataPacket.encrypted.ciphertext, &packet.encrypted.nonce, - key, + &gEeprom.ENC_KEY, 256 ); @@ -694,7 +694,7 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { PAYLOAD_LENGTH, dencryptedTxMessage, &dataPacket.encrypted.nonce, - key, + &gEeprom.ENC_KEY, 256); snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dencryptedTxMessage); diff --git a/settings.h b/settings.h index 38e7540f2..9cce838fa 100644 --- a/settings.h +++ b/settings.h @@ -247,7 +247,7 @@ typedef struct { uint8_t PASSWORD_WRONG_ATTEMPTS; #endif #ifdef ENABLE_ENCRYPTION - char ENC_KEY[16]; + char ENC_KEY[32]; #endif uint16_t VOX1_THRESHOLD; uint16_t VOX0_THRESHOLD; From 203a7f4e3ac7c74156df61d947e6a0fd826c128d Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 17:52:32 +0100 Subject: [PATCH 19/39] Fix loading first enc_key byte on boot. --- settings.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.c b/settings.c index 63ddffb22..da023dbf5 100644 --- a/settings.c +++ b/settings.c @@ -272,8 +272,8 @@ void SETTINGS_SaveChannelName(uint8_t channel, const char * name) void SETTINGS_SaveEncryptionKey() { // TODO: this should probably autoadjust to sizeof(gEeprom.ENC_KEY) - EEPROM_WriteBuffer(0x0F30, &gEeprom.ENC_KEY, true); - EEPROM_WriteBuffer(0x0F38, &gEeprom.ENC_KEY + 8, true); + EEPROM_WriteBuffer(0x0F30, gEeprom.ENC_KEY, true); + EEPROM_WriteBuffer(0x0F38, gEeprom.ENC_KEY + 8, true); } #endif From b1cbcccbd5aa9815cb8fa92d0e33749ef61f5564 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 17:57:49 +0100 Subject: [PATCH 20/39] Hash function implementation. --- helper/crypto.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ helper/crypto.h | 13 +++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/helper/crypto.c b/helper/crypto.c index 7ff421d7a..ed16899c3 100644 --- a/helper/crypto.c +++ b/helper/crypto.c @@ -19,6 +19,20 @@ #include "driver/bk4819.h" #include "driver/systick.h" +// salt used for hashing encryption key from eeprom used for sending packets +// we never actually use the key stored in eeprom directly +// actually salt should be [16] + key [16] and then we hash it and get encryption key [32] +static const uint8_t encryptionSalt[32] = { + 0xEF, 0x58, 0x0A, 0xC6, 0x12, 0x4A, 0xFA, 0x4F, 0xAE, 0x6F, 0x9D, 0x3C, 0xBB, 0x80, 0xAC, 0x4A, + 0xF3, 0x5E, 0x11, 0x69, 0xC7, 0x97, 0xFB, 0xC6, 0x27, 0x4F, 0xB7, 0x1A, 0xA7, 0xE5, 0x77, 0x9C +}; + +// salt used to display encryption key hash in menu +static const uint8_t displaySalt[32] = { + 0x22, 0xA1, 0xDA, 0x49, 0xA8, 0x22, 0x79, 0xBF, 0x2D, 0x98, 0xDE, 0xA8, 0x4F, 0xC3, 0x23, 0xF3, + 0x65, 0xB9, 0x69, 0x22, 0xB3, 0x6F, 0xB4, 0x59, 0xC7, 0x90, 0x10, 0x70, 0xFA, 0x51, 0xA0, 0x19 +}; + // Used for both encryption and decryption void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const void *key, int key_len) { @@ -62,4 +76,53 @@ void CRYPTO_Random(void *output, int len) } } + +// Generate salted hash +// output is always 8 bytes (64 bits), +// input can be upto 255 bytes +void CRYPTO_HashSalted(const void *input, void *output, const void *salt, int input_len, int salt_len) +{ + // FNV offset basis + union eight_bytes hash; + + hash.u64 = 0xcbf29ce484222325; + // FNV prime + const uint64_t fnvPrime = 0x100000001b3; + + // hash input + for(int i=0; i +static const uint8_t encryptionSalt[32]; +static const uint8_t displaySalt[32]; + +union eight_bytes { + uint64_t u64; + uint8_t b8[sizeof(uint64_t)]; +}; + // Used for both encryption and decryption void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const void *key, int key_len); void CRYPTO_Random(void *output, int len); -uint8_t CRYPTO_RandomByte(); \ No newline at end of file +uint8_t CRYPTO_RandomByte(); +void CRYPTO_DisplayHash(void *input, void *output, int input_len); +// void CRYPTO_EncryptionKeyHash(void *input, void *output, int input_len); +void CRYPTO_HashSalted(const void *input, void *output, const void *salt, int input_len, int salt_len); \ No newline at end of file From 5b02ce3e906dc4ab4c04cd9e1597a75007649325 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 18:09:37 +0100 Subject: [PATCH 21/39] fix key length to 16 bytes, cleaning --- app/menu.c | 2 ++ settings.c | 1 - settings.h | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/menu.c b/app/menu.c index 2bf2ddf44..628e98fd3 100644 --- a/app/menu.c +++ b/app/menu.c @@ -487,7 +487,9 @@ void MENU_AcceptSetting(void) #ifdef ENABLE_ENCRYPTION case MENU_ENC_KEY: + memset(gEeprom.ENC_KEY, 0, sizeof(gEeprom.ENC_KEY)); memmove(gEeprom.ENC_KEY, edit, sizeof(edit)); + memset(edit, 0, sizeof(edit)); gUpdateStatus = true; break; #endif diff --git a/settings.c b/settings.c index da023dbf5..199fa445d 100644 --- a/settings.c +++ b/settings.c @@ -271,7 +271,6 @@ void SETTINGS_SaveChannelName(uint8_t channel, const char * name) #ifdef ENABLE_ENCRYPTION void SETTINGS_SaveEncryptionKey() { - // TODO: this should probably autoadjust to sizeof(gEeprom.ENC_KEY) EEPROM_WriteBuffer(0x0F30, gEeprom.ENC_KEY, true); EEPROM_WriteBuffer(0x0F38, gEeprom.ENC_KEY + 8, true); } diff --git a/settings.h b/settings.h index 9cce838fa..38e7540f2 100644 --- a/settings.h +++ b/settings.h @@ -247,7 +247,7 @@ typedef struct { uint8_t PASSWORD_WRONG_ATTEMPTS; #endif #ifdef ENABLE_ENCRYPTION - char ENC_KEY[32]; + char ENC_KEY[16]; #endif uint16_t VOX1_THRESHOLD; uint16_t VOX0_THRESHOLD; From 21bea186e6c5686b8d0e5f2662bdca30676bc9e8 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 18:41:11 +0100 Subject: [PATCH 22/39] 256bit key generation function --- helper/crypto.c | 28 ++++++++++++++++------------ helper/crypto.h | 4 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/helper/crypto.c b/helper/crypto.c index ed16899c3..565820547 100644 --- a/helper/crypto.c +++ b/helper/crypto.c @@ -22,9 +22,11 @@ // salt used for hashing encryption key from eeprom used for sending packets // we never actually use the key stored in eeprom directly // actually salt should be [16] + key [16] and then we hash it and get encryption key [32] -static const uint8_t encryptionSalt[32] = { - 0xEF, 0x58, 0x0A, 0xC6, 0x12, 0x4A, 0xFA, 0x4F, 0xAE, 0x6F, 0x9D, 0x3C, 0xBB, 0x80, 0xAC, 0x4A, - 0xF3, 0x5E, 0x11, 0x69, 0xC7, 0x97, 0xFB, 0xC6, 0x27, 0x4F, 0xB7, 0x1A, 0xA7, 0xE5, 0x77, 0x9C +static const uint8_t encryptionSalt[4][8] = { + {0xEF, 0x58, 0x0A, 0xC6, 0x12, 0x4A, 0xFA, 0x4F}, + {0xAE, 0x6F, 0x9D, 0x3C, 0xBB, 0x80, 0xAC, 0x4A}, + {0xF3, 0x5E, 0x11, 0x69, 0xC7, 0x97, 0xFB, 0xC6}, + {0x27, 0x4F, 0xB7, 0x1A, 0xA7, 0xE5, 0x77, 0x9C} }; // salt used to display encryption key hash in menu @@ -82,9 +84,9 @@ void CRYPTO_Random(void *output, int len) // input can be upto 255 bytes void CRYPTO_HashSalted(const void *input, void *output, const void *salt, int input_len, int salt_len) { - // FNV offset basis union eight_bytes hash; - + + // FNV offset basis hash.u64 = 0xcbf29ce484222325; // FNV prime const uint64_t fnvPrime = 0x100000001b3; @@ -111,18 +113,20 @@ void CRYPTO_HashSalted(const void *input, void *output, const void *salt, int in // it is not used for an actual encryption void CRYPTO_DisplayHash(void *input, void *output, int input_len) { - (void)encryptionSalt; - // char stringHash[10]; CRYPTO_HashSalted(input, output, displaySalt, input_len, sizeof(displaySalt)); + // convert uint8_t to ASCII chars for(int i=0; i<8; i++){ ((uint8_t *)output)[i]=(((uint8_t *)output)[i] % 91) + 32; } } -// so to generate the 256bit key we need 4 hash operations -// void CRYPTO_EncryptionKeyHash(void *input, void *output, int input_len) -// { - -// } +// generate 32 bytes (256 bits) key by combining 4 x 8 byte hash of the same +// 10 byte (80 bits) encryption key (each hash with different salt) +void CRYPTO_Generate256BitKey(void *input, void *output, int input_len) +{ + for(int i=0; i<4; i++){ + CRYPTO_HashSalted(input, output+(i*8), encryptionSalt[i], input_len, sizeof(encryptionSalt[i])); + } +} \ No newline at end of file diff --git a/helper/crypto.h b/helper/crypto.h index f74b2faf1..99e11f840 100644 --- a/helper/crypto.h +++ b/helper/crypto.h @@ -16,7 +16,7 @@ #include -static const uint8_t encryptionSalt[32]; +static const uint8_t encryptionSalt[4][8]; static const uint8_t displaySalt[32]; union eight_bytes { @@ -29,5 +29,5 @@ void CRYPTO_Crypt(void *input, int input_len, void *output, void *nonce, const v void CRYPTO_Random(void *output, int len); uint8_t CRYPTO_RandomByte(); void CRYPTO_DisplayHash(void *input, void *output, int input_len); -// void CRYPTO_EncryptionKeyHash(void *input, void *output, int input_len); +void CRYPTO_Generate256BitKey(void *input, void *output, int input_len); void CRYPTO_HashSalted(const void *input, void *output, const void *salt, int input_len, int salt_len); \ No newline at end of file From 214068d1cab2423ee5688c81fb1c041dc7600c9c Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 19:06:11 +0100 Subject: [PATCH 23/39] key recalculated after changing enckey menu setting --- app/messenger.c | 9 ++------- helper/crypto.c | 2 ++ helper/crypto.h | 1 + settings.c | 6 ++++++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index 309cc5309..f62a3da69 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -58,11 +58,6 @@ uint8_t hasNewMessage = 0; uint8_t keyTickCounter = 0; -// unsigned char key[32] = { -// 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, -// 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f -// }; - // ----------------------------------------------------- void MSG_FSKSendData() { @@ -571,7 +566,7 @@ void MSG_SendPacket(union DataPacket packet) { PAYLOAD_LENGTH, dataPacket.encrypted.ciphertext, &packet.encrypted.nonce, - &gEeprom.ENC_KEY, + gEncryptionKey, 256 ); @@ -694,7 +689,7 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { PAYLOAD_LENGTH, dencryptedTxMessage, &dataPacket.encrypted.nonce, - &gEeprom.ENC_KEY, + gEncryptionKey, 256); snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dencryptedTxMessage); diff --git a/helper/crypto.c b/helper/crypto.c index 565820547..6d5769bb7 100644 --- a/helper/crypto.c +++ b/helper/crypto.c @@ -19,6 +19,8 @@ #include "driver/bk4819.h" #include "driver/systick.h" +u_int8_t gEncryptionKey[32]; + // salt used for hashing encryption key from eeprom used for sending packets // we never actually use the key stored in eeprom directly // actually salt should be [16] + key [16] and then we hash it and get encryption key [32] diff --git a/helper/crypto.h b/helper/crypto.h index 99e11f840..55f8d6c82 100644 --- a/helper/crypto.h +++ b/helper/crypto.h @@ -16,6 +16,7 @@ #include +extern uint8_t gEncryptionKey[32]; static const uint8_t encryptionSalt[4][8]; static const uint8_t displaySalt[32]; diff --git a/settings.c b/settings.c index 199fa445d..43f6cb039 100644 --- a/settings.c +++ b/settings.c @@ -26,6 +26,10 @@ #include "settings.h" #include "board.h" +#ifdef ENABLE_ENCRYPTION + #include "helper/crypto.h" +#endif + EEPROM_Config_t gEeprom; void SETTINGS_SaveVfoIndices(void) @@ -273,6 +277,8 @@ void SETTINGS_SaveEncryptionKey() { EEPROM_WriteBuffer(0x0F30, gEeprom.ENC_KEY, true); EEPROM_WriteBuffer(0x0F38, gEeprom.ENC_KEY + 8, true); + + CRYPTO_Generate256BitKey(gEeprom.ENC_KEY, gEncryptionKey, sizeof(gEeprom.ENC_KEY)); } #endif From 0d1a44c572e60552f2952bd6d4fd393792b35228 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 21:13:49 +0100 Subject: [PATCH 24/39] remove charge indicator logic, more aggressive size optimization --- app/app.c | 12 +++------ app/spectrum.c | 2 +- app/uart.c | 2 +- bitmaps.c | 13 ---------- bitmaps.h | 2 -- board.c | 3 +-- board.h | 2 +- helper/battery.c | 63 +++++++++++++++--------------------------------- helper/battery.h | 2 -- main.c | 6 ++--- ui/main.c | 19 --------------- ui/status.c | 10 +------- 12 files changed, 31 insertions(+), 105 deletions(-) diff --git a/app/app.c b/app/app.c index 38fa341dd..c17e7ae36 100644 --- a/app/app.c +++ b/app/app.c @@ -1419,9 +1419,9 @@ void APP_TimeSlice500ms(void) if (gReducedService) { - BOARD_ADC_GetBatteryInfo(&gBatteryCurrentVoltage, &gBatteryCurrent); + BOARD_ADC_GetBatteryInfo(&gBatteryCurrentVoltage); - if (gBatteryCurrent > 500 || gBatteryCalibration[3] < gBatteryCurrentVoltage) + if (gBatteryCalibration[3] < gBatteryCurrentVoltage) { #ifdef ENABLE_OVERLAY overlay_FLASH_RebootToBootloader(); @@ -1442,7 +1442,7 @@ void APP_TimeSlice500ms(void) if ((gBatteryCheckCounter & 1) == 0) { - BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++], &gBatteryCurrent); + BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++]); if (gBatteryVoltageIndex > 3) gBatteryVoltageIndex = 0; BATTERY_GetReadings(true); @@ -1452,12 +1452,8 @@ void APP_TimeSlice500ms(void) // regular display updates (once every 2 sec) - if need be if ((gBatteryCheckCounter & 3) == 0) { - if (gChargingWithTypeC || gSetting_battery_text > 0) + if (gSetting_battery_text > 0) gUpdateStatus = true; - #ifdef ENABLE_SHOW_CHARGE_LEVEL - if (gChargingWithTypeC) - gUpdateDisplay = true; - #endif } #ifdef ENABLE_FMRADIO diff --git a/app/spectrum.c b/app/spectrum.c index 95e1a8205..c1e012a4c 100644 --- a/app/spectrum.c +++ b/app/spectrum.c @@ -793,7 +793,7 @@ static void DrawStatus() { #endif GUI_DisplaySmallest(String, 0, 1, true, true); - BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryCheckCounter++ % 4], &gBatteryCurrent); + BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryCheckCounter++ % 4]); uint16_t voltage = (gBatteryVoltages[0] + gBatteryVoltages[1] + gBatteryVoltages[2] + gBatteryVoltages[3]) / diff --git a/app/uart.c b/app/uart.c index 60cf0b668..b11dd2642 100644 --- a/app/uart.c +++ b/app/uart.c @@ -316,7 +316,7 @@ static void CMD_0529(void) Reply.Header.Size = sizeof(Reply.Data); // Original doesn't actually send current! - BOARD_ADC_GetBatteryInfo(&Reply.Data.Voltage, &Reply.Data.Current); + BOARD_ADC_GetBatteryInfo(&Reply.Data.Voltage); SendReply(&Reply, sizeof(Reply)); } diff --git a/bitmaps.c b/bitmaps.c index 8500def1c..c44dd8c95 100644 --- a/bitmaps.c +++ b/bitmaps.c @@ -105,19 +105,6 @@ const uint8_t BITMAP_BatteryLevel[2] = }; #endif -const uint8_t BITMAP_USB_C[9] = -{ // USB symbol - 0b00000000, - 0b00011100, - 0b00100111, - 0b01000100, - 0b01000100, - 0b01000100, - 0b01000100, - 0b00100111, - 0b00011100 -}; - const uint8_t BITMAP_KeyLock[6] = { // teeny padlock symbol 0b00000000, diff --git a/bitmaps.h b/bitmaps.h index c9c394c0d..5b1367fae 100644 --- a/bitmaps.h +++ b/bitmaps.h @@ -11,8 +11,6 @@ extern const uint8_t BITMAP_RX[8]; extern const uint8_t BITMAP_BatteryLevel[2]; extern const uint8_t BITMAP_BatteryLevel1[17]; -extern const uint8_t BITMAP_USB_C[9]; - extern const uint8_t BITMAP_KeyLock[6]; extern const uint8_t BITMAP_F_Key[6]; diff --git a/board.c b/board.c index 2e5017fb1..17c030eca 100644 --- a/board.c +++ b/board.c @@ -491,12 +491,11 @@ void BOARD_ADC_Init(void) ADC_SoftReset(); } -void BOARD_ADC_GetBatteryInfo(uint16_t *pVoltage, uint16_t *pCurrent) +void BOARD_ADC_GetBatteryInfo(uint16_t *pVoltage) { ADC_Start(); while (!ADC_CheckEndOfConversion(ADC_CH9)) {} *pVoltage = ADC_GetValue(ADC_CH4); - *pCurrent = ADC_GetValue(ADC_CH9); } void BOARD_Init(void) diff --git a/board.h b/board.h index 6247f5c08..9fef7d8b8 100644 --- a/board.h +++ b/board.h @@ -24,7 +24,7 @@ void BOARD_FLASH_Init(void); void BOARD_GPIO_Init(void); void BOARD_PORTCON_Init(void); void BOARD_ADC_Init(void); -void BOARD_ADC_GetBatteryInfo(uint16_t *pVoltage, uint16_t *pCurrent); +void BOARD_ADC_GetBatteryInfo(uint16_t *pVoltage); void BOARD_Init(void); void BOARD_EEPROM_Init(void); void BOARD_EEPROM_LoadCalibration(void); diff --git a/helper/battery.c b/helper/battery.c index 13f43c13b..ec077babe 100644 --- a/helper/battery.c +++ b/helper/battery.c @@ -26,11 +26,9 @@ uint16_t gBatteryCalibration[6]; uint16_t gBatteryCurrentVoltage; -uint16_t gBatteryCurrent; uint16_t gBatteryVoltages[4]; uint16_t gBatteryVoltageAverage; uint8_t gBatteryDisplayLevel; -bool gChargingWithTypeC; bool gLowBatteryBlink; bool gLowBattery; bool gLowBatteryConfirmed; @@ -123,28 +121,6 @@ void BATTERY_GetReadings(const bool bDisplayBatteryLevel) if ((gScreenToDisplay == DISPLAY_MENU) && UI_MENU_GetCurrentMenuId() == MENU_VOL) gUpdateDisplay = true; - if (gBatteryCurrent < 501) - { - if (gChargingWithTypeC) - { - gUpdateStatus = true; - gUpdateDisplay = true; - } - - gChargingWithTypeC = false; - } - else - { - if (!gChargingWithTypeC) - { - gUpdateStatus = true; - gUpdateDisplay = true; - BACKLIGHT_TurnOn(); - } - - gChargingWithTypeC = true; - } - if (PreviousBatteryLevel != gBatteryDisplayLevel) { if(gBatteryDisplayLevel > 2) @@ -181,42 +157,41 @@ void BATTERY_TimeSlice500ms(void) if (lowBatteryCountdown < lowBatteryPeriod) { - if (lowBatteryCountdown == lowBatteryPeriod-1 && !gChargingWithTypeC && !gLowBatteryConfirmed) + if (lowBatteryCountdown == lowBatteryPeriod-1 && !gLowBatteryConfirmed) AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP); } else { lowBatteryCountdown = 0; - if (!gChargingWithTypeC) - { // not on charge - if(!gLowBatteryConfirmed) { - AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP); + + if(!gLowBatteryConfirmed) { + AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP); #ifdef ENABLE_VOICE - AUDIO_SetVoiceID(0, VOICE_ID_LOW_VOLTAGE); + AUDIO_SetVoiceID(0, VOICE_ID_LOW_VOLTAGE); #endif - } - if (gBatteryDisplayLevel == 0) - { + } + if (gBatteryDisplayLevel == 0) + { #ifdef ENABLE_VOICE - AUDIO_PlaySingleVoice(true); + AUDIO_PlaySingleVoice(true); #endif - gReducedService = true; + gReducedService = true; - //if (gCurrentFunction != FUNCTION_POWER_SAVE) - FUNCTION_Select(FUNCTION_POWER_SAVE); + //if (gCurrentFunction != FUNCTION_POWER_SAVE) + FUNCTION_Select(FUNCTION_POWER_SAVE); - ST7565_HardwareReset(); + ST7565_HardwareReset(); - if (gEeprom.BACKLIGHT_TIME < (ARRAY_SIZE(gSubMenu_BACKLIGHT) - 1)) - BACKLIGHT_TurnOff(); // turn the backlight off - } + if (gEeprom.BACKLIGHT_TIME < (ARRAY_SIZE(gSubMenu_BACKLIGHT) - 1)) + BACKLIGHT_TurnOff(); // turn the backlight off + } #ifdef ENABLE_VOICE - else - AUDIO_PlaySingleVoice(false); + else + AUDIO_PlaySingleVoice(false); #endif - } + } } } diff --git a/helper/battery.h b/helper/battery.h index c951bd96d..f6fa36ae5 100644 --- a/helper/battery.h +++ b/helper/battery.h @@ -22,11 +22,9 @@ extern uint16_t gBatteryCalibration[6]; extern uint16_t gBatteryCurrentVoltage; -extern uint16_t gBatteryCurrent; extern uint16_t gBatteryVoltages[4]; extern uint16_t gBatteryVoltageAverage; extern uint8_t gBatteryDisplayLevel; -extern bool gChargingWithTypeC; extern bool gLowBatteryBlink; extern bool gLowBattery; extern bool gLowBatteryConfirmed; diff --git a/main.c b/main.c index 120007b90..858b8125a 100644 --- a/main.c +++ b/main.c @@ -79,7 +79,7 @@ void Main(void) memset(gDTMF_String, '-', sizeof(gDTMF_String)); gDTMF_String[sizeof(gDTMF_String) - 1] = 0; - BOARD_ADC_GetBatteryInfo(&gBatteryCurrentVoltage, &gBatteryCurrent); + BOARD_ADC_GetBatteryInfo(&gBatteryCurrentVoltage); BOARD_EEPROM_Init(); @@ -98,7 +98,7 @@ void Main(void) BK4819_SetAGC(gEeprom.RX_AGC!=RX_AGC_OFF); for (i = 0; i < ARRAY_SIZE(gBatteryVoltages); i++) - BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[i], &gBatteryCurrent); + BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[i]); BATTERY_GetReadings(false); @@ -140,7 +140,7 @@ void Main(void) gDebounceCounter = 0; } - if (!gChargingWithTypeC && gBatteryDisplayLevel == 0) + if (gBatteryDisplayLevel == 0) { FUNCTION_Select(FUNCTION_POWER_SAVE); diff --git a/ui/main.c b/ui/main.c index 84f13f515..e65ea0d16 100644 --- a/ui/main.c +++ b/ui/main.c @@ -708,25 +708,6 @@ void UI_DisplayMain(void) UI_PrintStringSmall(String, 2, 0, 3); } #endif - -#ifdef ENABLE_SHOW_CHARGE_LEVEL - else if (gChargingWithTypeC) - { // charging .. show the battery state - if (gScreenToDisplay != DISPLAY_MAIN -#ifdef ENABLE_DTMF_CALLING - || gDTMF_CallState != DTMF_CALL_STATE_NONE -#endif - ) - return; - - center_line = CENTER_LINE_CHARGE_DATA; - - sprintf(String, "Charge %u.%02uV %u%%", - gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100, - BATTERY_VoltsToPercent(gBatteryVoltageAverage)); - UI_PrintStringSmall(String, 2, 0, 3); - } -#endif } } diff --git a/ui/status.c b/ui/status.c index a95e187c8..e960b4077 100644 --- a/ui/status.c +++ b/ui/status.c @@ -182,9 +182,6 @@ void UI_DisplayStatus() unsigned int x2 = LCD_WIDTH - sizeof(BITMAP_BatteryLevel1) - 3; - if (gChargingWithTypeC) - x2 -= sizeof(BITMAP_USB_C); // the radio is on charge - switch (gSetting_battery_text) { default: @@ -215,12 +212,7 @@ void UI_DisplayStatus() } // move to right side of the screen - x = LCD_WIDTH - sizeof(BITMAP_BatteryLevel1) - sizeof(BITMAP_USB_C); - - // USB-C charge indicator - if (gChargingWithTypeC) - memmove(line + x, BITMAP_USB_C, sizeof(BITMAP_USB_C)); - x += sizeof(BITMAP_USB_C); + x = LCD_WIDTH - sizeof(BITMAP_BatteryLevel1); // BATTERY LEVEL indicator UI_DrawBattery(line + x, gBatteryDisplayLevel, gLowBatteryBlink); From ce87fd3fdbc988ed5c9e5fc6d14b67d32719d5d8 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 21:15:31 +0100 Subject: [PATCH 25/39] fix bug too short string --- ui/menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/menu.c b/ui/menu.c index 9b29628cc..510f551d0 100644 --- a/ui/menu.c +++ b/ui/menu.c @@ -1002,7 +1002,7 @@ void UI_DisplayMenu(void) void MENU_PrintNotAllowed() { - char String[7]; + char String[8]; strcpy(String, "NOT"); UI_PrintString(String, menu_item_x1, menu_item_x2, 0, 8); strcpy(String, "ALLOWED"); From ab93430b89ae6f345521256df5822cf9b139d79b Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 21:34:50 +0100 Subject: [PATCH 26/39] display hash value of the key --- ui/menu.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ui/menu.c b/ui/menu.c index 510f551d0..fb5c8e9c5 100644 --- a/ui/menu.c +++ b/ui/menu.c @@ -35,6 +35,9 @@ #include "ui/inputbox.h" #include "ui/menu.h" #include "ui/ui.h" +#ifdef ENABLE_ENCRYPTION + #include "helper/crypto.h" +#endif const t_menu_item MenuList[] = { @@ -729,8 +732,7 @@ void UI_DisplayMenu(void) { if (!gIsInSubMenu) { // show placeholder in main menu - // strcpy(String, "****"); - sprintf(String, "%s", gEeprom.ENC_KEY); + strcpy(String, "****"); UI_PrintString(String, menu_item_x1, menu_item_x2, 2, 8); } else @@ -742,7 +744,12 @@ void UI_DisplayMenu(void) UI_PrintString( "^", (menu_item_x1 -2) + (8 * edit_index), 0, 4, 8); } else{ - strcpy(String, "hash"); + strcpy(String, "hashed value"); + UI_PrintStringSmall(String, 20, 0, 5); + + memset(String, 0, sizeof(String)); + + CRYPTO_DisplayHash(gEeprom.ENC_KEY, String, sizeof(gEeprom.ENC_KEY)); UI_PrintString(String, (menu_item_x1 -2), 0, 2, 8); } } From 16002f84be15f744d41e52cc74497815654ae312 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 21:56:47 +0100 Subject: [PATCH 27/39] cleanup --- app/menu.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/menu.c b/app/menu.c index 628e98fd3..4556820a6 100644 --- a/app/menu.c +++ b/app/menu.c @@ -952,10 +952,6 @@ void MENU_ShowCurrentSetting(void) gSubMenuSelection = gEeprom.MrChannel[gEeprom.TX_VFO]; break; - // case MENU_ENC_KEY: - // gSubMenuSelection = 1; - // break; - case MENU_SAVE: gSubMenuSelection = gEeprom.BATTERY_SAVE; break; From 2cba1802ba6bd45df7b6c3a2310a49d302d3f81f Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 21:57:50 +0100 Subject: [PATCH 28/39] cleanup --- app/menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/menu.c b/app/menu.c index 4556820a6..99ad3c0db 100644 --- a/app/menu.c +++ b/app/menu.c @@ -1455,7 +1455,7 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) #if 1 if (UI_MENU_GetCurrentMenuId() == MENU_DEL_CH || UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) if (!RADIO_CheckValidChannel(gSubMenuSelection, false, 0)) - return; // invalid channel + return; // invalid channel #endif gAskForConfirmation = 0; From 9ec106ff5acbcfaefc3548620fe9e99fdfc2ed94 Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 21:59:42 +0100 Subject: [PATCH 29/39] cleanup --- app/menu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app/menu.c b/app/menu.c index 99ad3c0db..24968c549 100644 --- a/app/menu.c +++ b/app/menu.c @@ -1476,7 +1476,6 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) if (edit_index < 0) { // enter encryption key edit mode // pad the encryption key out with '_' - // TODO: Extract to shared function - common with MENU_MEM_NAME below edit_index = strlen(edit); while (edit_index < 10) edit[edit_index++] = '_'; From 357b1f5517932255fdc0783798662d2c6cba3f2c Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 22:01:27 +0100 Subject: [PATCH 30/39] cleanup --- app/menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/menu.c b/app/menu.c index 24968c549..521f69ca6 100644 --- a/app/menu.c +++ b/app/menu.c @@ -1500,7 +1500,7 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) if (edit_index < 0) { // enter channel name edit mode if (!RADIO_CheckValidChannel(gSubMenuSelection, false, 0)) - return; + return; SETTINGS_FetchChannelName(edit, gSubMenuSelection); From 3b2c3b9f51272406b5f710882c5048008d39110b Mon Sep 17 00:00:00 2001 From: Nunu Date: Thu, 25 Jan 2024 22:12:51 +0100 Subject: [PATCH 31/39] cleanup --- helper/crypto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/crypto.c b/helper/crypto.c index 6d5769bb7..ddf905798 100644 --- a/helper/crypto.c +++ b/helper/crypto.c @@ -23,7 +23,7 @@ u_int8_t gEncryptionKey[32]; // salt used for hashing encryption key from eeprom used for sending packets // we never actually use the key stored in eeprom directly -// actually salt should be [16] + key [16] and then we hash it and get encryption key [32] +// 4 salts for each 8 bytes chunks of the encryption key static const uint8_t encryptionSalt[4][8] = { {0xEF, 0x58, 0x0A, 0xC6, 0x12, 0x4A, 0xFA, 0x4F}, {0xAE, 0x6F, 0x9D, 0x3C, 0xBB, 0x80, 0xAC, 0x4A}, From ac889cfd73007ce3f26f35503d13dbdd51372902 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 11:23:24 +0100 Subject: [PATCH 32/39] fm radio remove unused keys --- app/fm.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/fm.c b/app/fm.c index 2ea52afe9..5eb56e8a2 100644 --- a/app/fm.c +++ b/app/fm.c @@ -84,15 +84,8 @@ void FM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) case KEY_EXIT: Key_EXIT(); break; - case KEY_F: - GENERIC_Key_F(bKeyPressed, bKeyHeld); - break; - case KEY_PTT: - GENERIC_Key_PTT(bKeyPressed); - break; default: - if (!bKeyHeld && bKeyPressed) - gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; + gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; break; } } From 146342e0f08199c464dad78a144794df58770493 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 11:24:54 +0100 Subject: [PATCH 33/39] f1 f2 only trigger assigned actions in the main screen --- app/app.c | 78 ++++++++++++++++++++++--------------------------------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/app/app.c b/app/app.c index c17e7ae36..8595805cb 100644 --- a/app/app.c +++ b/app/app.c @@ -915,17 +915,6 @@ void APP_Update(void) } } -#ifdef ENABLE_FMRADIO - if (gScheduleFM && - gFM_ScanState != FM_SCAN_OFF && - gCurrentFunction != FUNCTION_MONITOR && - gCurrentFunction != FUNCTION_RECEIVE && - gCurrentFunction != FUNCTION_TRANSMIT) - { // switch to FM radio mode - FM_Start(); - gScheduleFM = false; - } -#endif #ifdef ENABLE_VOX if (gEeprom.VOX_SWITCH) @@ -1457,7 +1446,7 @@ void APP_TimeSlice500ms(void) } #ifdef ENABLE_FMRADIO - if ((gFM_ScanState == FM_SCAN_OFF || gAskToSave) && !gCssBackgroundScan) + if (gAskToSave && !gCssBackgroundScan) #else if (!gCssBackgroundScan) #endif @@ -1889,50 +1878,45 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) } #endif } - else if (Key != KEY_SIDE1 && Key != KEY_SIDE2) { - switch (gScreenToDisplay) { - case DISPLAY_MAIN: + switch (gScreenToDisplay) { + case DISPLAY_MAIN: + if ((Key == KEY_SIDE1 || Key == KEY_SIDE2) && !SCANNER_IsScanning()) + { + ACTION_Handle(Key, bKeyPressed, bKeyHeld); + } + else MAIN_ProcessKeys(Key, bKeyPressed, bKeyHeld); - break; + + break; #ifdef ENABLE_FMRADIO - case DISPLAY_FM: - FM_ProcessKeys(Key, bKeyPressed, bKeyHeld); - break; + case DISPLAY_FM: + FM_ProcessKeys(Key, bKeyPressed, bKeyHeld); + break; #endif - case DISPLAY_MENU: - MENU_ProcessKeys(Key, bKeyPressed, bKeyHeld); + case DISPLAY_MENU: + MENU_ProcessKeys(Key, bKeyPressed, bKeyHeld); + break; + + #ifdef ENABLE_MESSENGER + case DISPLAY_MSG: + MSG_ProcessKeys(Key, bKeyPressed, bKeyHeld); break; - - #ifdef ENABLE_MESSENGER - case DISPLAY_MSG: - MSG_ProcessKeys(Key, bKeyPressed, bKeyHeld); - break; - #endif + #endif - case DISPLAY_SCANNER: - SCANNER_ProcessKeys(Key, bKeyPressed, bKeyHeld); - break; + case DISPLAY_SCANNER: + SCANNER_ProcessKeys(Key, bKeyPressed, bKeyHeld); + break; #ifdef ENABLE_AIRCOPY - case DISPLAY_AIRCOPY: - AIRCOPY_ProcessKeys(Key, bKeyPressed, bKeyHeld); - break; + case DISPLAY_AIRCOPY: + AIRCOPY_ProcessKeys(Key, bKeyPressed, bKeyHeld); + break; #endif - case DISPLAY_INVALID: - default: - break; - } - } - else -#ifdef ENABLE_AIRCOPY - if (!SCANNER_IsScanning() && gScreenToDisplay != DISPLAY_AIRCOPY) -#else - if (!SCANNER_IsScanning()) -#endif - { - ACTION_Handle(Key, bKeyPressed, bKeyHeld); + case DISPLAY_INVALID: + default: + break; } - else if (!bKeyHeld && bKeyPressed) + if (!bKeyHeld && bKeyPressed) gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; } From b27adf80ed919b66d6c1b95c2b751f9bd4405d77 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 11:26:05 +0100 Subject: [PATCH 34/39] more agressive gcc optimizations - might break things --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 38d9c5fc3..e97cc71b3 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ENABLE_LTO := 1 # ---- STOCK QUANSHENG FERATURES ---- ENABLE_UART := 1 ENABLE_AIRCOPY := 0 -ENABLE_FMRADIO := 0 +ENABLE_FMRADIO := 1 ENABLE_NOAA := 0 ENABLE_VOICE := 0 ENABLE_VOX := 1 @@ -227,7 +227,10 @@ endif CFLAGS = ifeq ($(ENABLE_CLANG),0) - CFLAGS += -Os -Wall -Werror -mcpu=cortex-m0 -fno-builtin -fshort-enums -fno-delete-null-pointer-checks -std=c11 -MMD + # Highest optimization settings (possible breaking changes): + CFLAGS += -Oz -mcpu=cortex-m0 -fno-delete-null-pointer-checks -std=c11 -MMD + # Standard settings: + #CFLAGS += -Os -Wall -Werror -mcpu=cortex-m0 -fno-builtin -fshort-enums -fno-delete-null-pointer-checks -std=c11 -MMD #CFLAGS += -Os -Wall -Werror -mcpu=cortex-m0 -fno-builtin -fshort-enums -fno-delete-null-pointer-checks -std=c99 -MMD #CFLAGS += -Os -Wall -Werror -mcpu=cortex-m0 -fno-builtin -fshort-enums -fno-delete-null-pointer-checks -std=gnu99 -MMD #CFLAGS += -Os -Wall -Werror -mcpu=cortex-m0 -fno-builtin -fshort-enums -fno-delete-null-pointer-checks -std=gnu11 -MMD From 72073b6ac28c0e6b0472515aabe96036de565843 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 11:47:27 +0100 Subject: [PATCH 35/39] more fm radio trimming --- app/action.c | 30 ------------------------------ app/generic.c | 18 +++--------------- board.c | 3 --- misc.c | 6 ------ misc.h | 9 --------- scheduler.c | 8 ++++---- settings.h | 5 ----- 7 files changed, 7 insertions(+), 72 deletions(-) diff --git a/app/action.c b/app/action.c index 7acf2b475..dc85d92ae 100644 --- a/app/action.c +++ b/app/action.c @@ -102,42 +102,12 @@ void ACTION_Monitor(void) RADIO_SetupRegisters(true); -#ifdef ENABLE_FMRADIO - if (gFmRadioMode) { - FM_Start(); - gRequestDisplayScreen = DISPLAY_FM; - } - else -#endif gRequestDisplayScreen = gScreenToDisplay; } void ACTION_Scan(bool bRestart) { (void)bRestart; -#ifdef ENABLE_FMRADIO - if (gFmRadioMode) - { - if (gCurrentFunction != FUNCTION_RECEIVE && - gCurrentFunction != FUNCTION_MONITOR && - gCurrentFunction != FUNCTION_TRANSMIT) - { - GUI_SelectNextDisplay(DISPLAY_FM); - - gMonitor = false; - - if (gFM_ScanState != FM_SCAN_OFF) - { - FM_Start(); - -#ifdef ENABLE_VOICE - gAnotherVoiceID = VOICE_ID_SCANNING_STOP; -#endif - } - } - return; - } -#endif if (!SCANNER_IsScanning()) { // not scanning diff --git a/app/generic.c b/app/generic.c index f8a9f2362..13ba098b9 100644 --- a/app/generic.c +++ b/app/generic.c @@ -61,13 +61,9 @@ void GENERIC_Key_F(bool bKeyPressed, bool bKeyHeld) } else // released { - #ifdef ENABLE_FMRADIO - if ((gFmRadioMode || gScreenToDisplay != DISPLAY_MAIN) && gScreenToDisplay != DISPLAY_FM) - return; - #else - if (gScreenToDisplay != DISPLAY_MAIN) - return; - #endif + + if (gScreenToDisplay != DISPLAY_MAIN) + return; gWasFKeyPressed = !gWasFKeyPressed; // toggle F function @@ -90,14 +86,6 @@ void GENERIC_Key_F(bool bKeyPressed, bool bKeyHeld) return; } - #ifdef ENABLE_FMRADIO - if (gFM_ScanState == FM_SCAN_OFF) // not scanning - { - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; - return; - } - #endif - gBeepToPlay = BEEP_440HZ_500MS; gPttWasReleased = true; diff --git a/board.c b/board.c index 17c030eca..f75dc524a 100644 --- a/board.c +++ b/board.c @@ -505,9 +505,6 @@ void BOARD_Init(void) BACKLIGHT_InitHardware(); BOARD_ADC_Init(); ST7565_Init(true); - #ifdef ENABLE_FMRADIO - BK1080_Init(0, false); - #endif CRC_Init(); } diff --git a/misc.c b/misc.c index 6ebc97dfd..2972d5eba 100644 --- a/misc.c +++ b/misc.c @@ -171,9 +171,6 @@ bool gFlagResetVfos; bool gRequestSaveVFO; uint8_t gRequestSaveChannel; bool gRequestSaveSettings; -#ifdef ENABLE_FMRADIO - bool gRequestSaveFM; -#endif bool gFlagPrepareTX; bool gFlagAcceptSetting; @@ -182,9 +179,6 @@ bool gFlagRefreshSetting; bool gFlagSaveVfo; bool gFlagSaveSettings; bool gFlagSaveChannel; -#ifdef ENABLE_FMRADIO - bool gFlagSaveFM; -#endif bool g_CDCSS_Lost; uint8_t gCDCSSCodeType; bool g_CTCSS_Lost; diff --git a/misc.h b/misc.h index eda934e6e..99014cd1b 100644 --- a/misc.h +++ b/misc.h @@ -280,9 +280,6 @@ extern bool gFlagResetVfos; extern bool gRequestSaveVFO; extern uint8_t gRequestSaveChannel; extern bool gRequestSaveSettings; -#ifdef ENABLE_FMRADIO - extern bool gRequestSaveFM; -#endif extern uint8_t gKeypadLocked; extern bool gFlagPrepareTX; @@ -292,9 +289,6 @@ extern bool gFlagRefreshSetting; // refresh menu display extern bool gFlagSaveVfo; extern bool gFlagSaveSettings; extern bool gFlagSaveChannel; -#ifdef ENABLE_FMRADIO - extern bool gFlagSaveFM; -#endif extern bool g_CDCSS_Lost; extern uint8_t gCDCSSCodeType; extern bool g_CTCSS_Lost; @@ -335,9 +329,6 @@ extern uint8_t gFSKWriteIndex; extern volatile bool gNextTimeslice; extern bool gUpdateDisplay; extern bool gF_LOCK; -#ifdef ENABLE_FMRADIO - extern uint8_t gFM_ChannelPosition; -#endif extern uint8_t gShowChPrefix; extern volatile uint8_t gFoundCDCSSCountdown_10ms; extern volatile uint8_t gFoundCTCSSCountdown_10ms; diff --git a/scheduler.c b/scheduler.c index faa16cd82..ca66c71f5 100644 --- a/scheduler.c +++ b/scheduler.c @@ -99,10 +99,10 @@ void SystickHandler(void) DECREMENT_AND_TRIGGER(gCountdownToPlayNextVoice_10ms, gFlagPlayQueuedVoice); #endif - #ifdef ENABLE_FMRADIO - if (gFM_ScanState != FM_SCAN_OFF && gCurrentFunction != FUNCTION_MONITOR) - if (gCurrentFunction != FUNCTION_TRANSMIT && gCurrentFunction != FUNCTION_RECEIVE) - DECREMENT_AND_TRIGGER(gFmPlayCountdown_10ms, gScheduleFM); + #ifdef ENABLE_FMRADIO //gFM_ScanState is never different than FM_SCAN_OFF + // if (gFM_ScanState != FM_SCAN_OFF && gCurrentFunction != FUNCTION_MONITOR) + // if (gCurrentFunction != FUNCTION_TRANSMIT && gCurrentFunction != FUNCTION_RECEIVE) + // DECREMENT_AND_TRIGGER(gFmPlayStandardCountdown_10ms, gScheduleFM); #endif #ifdef ENABLE_VOX diff --git a/settings.h b/settings.h index 38e7540f2..0b5e526f7 100644 --- a/settings.h +++ b/settings.h @@ -154,12 +154,7 @@ typedef struct { uint8_t field8_0xb; #ifdef ENABLE_FMRADIO - uint16_t FM_SelectedFrequency; - uint8_t FM_SelectedChannel; - bool FM_IsMrMode; uint16_t FM_FrequencyPlaying; - uint16_t FM_LowerLimit; - uint16_t FM_UpperLimit; #endif uint8_t SQUELCH_LEVEL; From 9ec7bf83bb5b67477382db381a5b939659d35372 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 11:47:54 +0100 Subject: [PATCH 36/39] fix beep type when correct option is clicked --- app/app.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/app.c b/app/app.c index 8595805cb..a8d8466be 100644 --- a/app/app.c +++ b/app/app.c @@ -1914,10 +1914,9 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) #endif case DISPLAY_INVALID: default: + gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; break; } - if (!bKeyHeld && bKeyPressed) - gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; } Skip: From d7d635552a4773dec282019fb06afba69acde583 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 13:17:45 +0100 Subject: [PATCH 37/39] clean packet structure, fix disabled encryption flag, fix rx of unencrypted msgs --- app/messenger.c | 56 ++++++++++++++++++++++++------------------------- app/messenger.h | 18 ++++++---------- board.c | 7 ++++--- 3 files changed, 38 insertions(+), 43 deletions(-) diff --git a/app/messenger.c b/app/messenger.c index f62a3da69..5fbd50477 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -542,7 +542,7 @@ void MSG_SendPacket(union DataPacket packet) { if ( msgStatus != READY ) return; - if ( strlen(packet.unencrypted.payload) > 0 && (TX_freq_check(gCurrentVfo->pTX->Frequency) == 0) ) { + if ( strlen(packet.data.payload) > 0 && (TX_freq_check(gCurrentVfo->pTX->Frequency) == 0) ) { msgStatus = SENDING; @@ -554,23 +554,23 @@ void MSG_SendPacket(union DataPacket packet) { // later refactor to not use global state but pass dataPacket type everywhere dataPacket = packet; #ifdef ENABLE_ENCRYPTION - if(packet.unencrypted.header == ENCRYPTED_MESSAGE_PACKET){ + if(packet.data.header == ENCRYPTED_MESSAGE_PACKET){ char nonce[NONCE_LENGTH]; CRYPTO_Random(nonce, NONCE_LENGTH); // this is wat happens when we have global state - memcpy(packet.encrypted.nonce, nonce, NONCE_LENGTH); + memcpy(packet.data.nonce, nonce, NONCE_LENGTH); CRYPTO_Crypt( - packet.encrypted.ciphertext, + packet.data.payload, PAYLOAD_LENGTH, - dataPacket.encrypted.ciphertext, - &packet.encrypted.nonce, + dataPacket.data.payload, + &packet.data.nonce, gEncryptionKey, 256 ); - memcpy(dataPacket.encrypted.nonce, nonce, sizeof(dataPacket.encrypted.nonce)); + memcpy(dataPacket.data.nonce, nonce, sizeof(dataPacket.data.nonce)); } #endif @@ -598,11 +598,11 @@ void MSG_SendPacket(union DataPacket packet) { BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false); MSG_EnableRX(true); - if (packet.unencrypted.header != ACK_PACKET) { + if (packet.data.header != ACK_PACKET) { moveUP(rxMessage); - sprintf(rxMessage[3], "> %s", packet.encrypted.ciphertext); + sprintf(rxMessage[3], "> %s", packet.data.payload); memset(lastcMessage, 0, sizeof(lastcMessage)); - memcpy(lastcMessage, packet.encrypted.ciphertext, PAYLOAD_LENGTH); + memcpy(lastcMessage, packet.data.payload, PAYLOAD_LENGTH); cIndex = 0; prevKey = 0; prevLetter = 0; @@ -663,8 +663,7 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { if (gFSKWriteIndex > 2) { - // If there's three 0x1b bytes, then it's a service message - if (dataPacket.unencrypted.header == ACK_PACKET) { + if (dataPacket.data.header == ACK_PACKET) { #ifdef ENABLE_MESSENGER_DELIVERY_NOTIFICATION #ifdef ENABLE_MESSENGER_UART UART_printf("SVC= INVALID_PACKET) { + if (dataPacket.data.header >= INVALID_PACKET) { snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "ERROR: INVALID PACKET."); } else { #ifdef ENABLE_ENCRYPTION - char dencryptedTxMessage[PAYLOAD_LENGTH]; - // memset(dencryptedTxMessage,0,sizeof(dencryptedTxMessage)); - - if(dataPacket.unencrypted.header == ENCRYPTED_MESSAGE_PACKET) - CRYPTO_Crypt(dataPacket.encrypted.ciphertext, + if(dataPacket.data.header == ENCRYPTED_MESSAGE_PACKET) + { + CRYPTO_Crypt(dataPacket.data.payload, PAYLOAD_LENGTH, - dencryptedTxMessage, - &dataPacket.encrypted.nonce, + dataPacket.data.payload, + &dataPacket.data.nonce, gEncryptionKey, 256); - - snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dencryptedTxMessage); + } + snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dataPacket.data.payload); #else - snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dataPacket.unencrypted.payload); + snprintf(rxMessage[3], PAYLOAD_LENGTH + 2, "< %s", dataPacket.data.payload); #endif } @@ -718,10 +715,9 @@ void MSG_StorePacket(const uint16_t interrupt_bits) { gFSKWriteIndex = 0; // Transmit a message to the sender that we have received the message (Unless it's a service message) - if (dataPacket.unencrypted.header!=ACK_PACKET) { - dataPacket.unencrypted.header=ACK_PACKET; + if (dataPacket.data.header!=ACK_PACKET) { + dataPacket.data.header=ACK_PACKET; MSG_SendPacket(dataPacket); - // MSG_Send("\x1b\x1b\x1bRCVD ", true); } } } @@ -837,8 +833,12 @@ void MSG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { case KEY_MENU: // Send message memset(dataPacket.serializedArray,0,sizeof(dataPacket.serializedArray)); - dataPacket.unencrypted.header=ENCRYPTED_MESSAGE_PACKET; - memcpy(dataPacket.unencrypted.payload, cMessage, sizeof(dataPacket.unencrypted.payload)); + #ifdef ENABLE_ENCRYPTION + dataPacket.data.header=ENCRYPTED_MESSAGE_PACKET; + #else + dataPacket.data.header=MESSAGE_PACKET; + #endif + memcpy(dataPacket.data.payload, cMessage, sizeof(dataPacket.data.payload)); MSG_SendPacket(dataPacket); break; case KEY_EXIT: diff --git a/app/messenger.h b/app/messenger.h index f2ceb24dc..bc496e1f5 100644 --- a/app/messenger.h +++ b/app/messenger.h @@ -34,27 +34,21 @@ typedef enum MsgStatus { } MsgStatus; typedef enum PacketType { - MESSAGE_PACKET, + MESSAGE_PACKET = 100u, ENCRYPTED_MESSAGE_PACKET, - ACK_PACKET, - INVALID_PACKET + ACK_PACKET, + INVALID_PACKET } PacketType; // Data Packet definition // 2024 kamilsss655 union DataPacket { - struct{ - uint8_t header; - uint8_t ciphertext[PAYLOAD_LENGTH]; - unsigned char nonce[NONCE_LENGTH]; - // uint8_t signature[SIGNATURE_LENGTH]; - } encrypted; - struct{ uint8_t header; - char payload[PAYLOAD_LENGTH]; + uint8_t payload[PAYLOAD_LENGTH]; + unsigned char nonce[NONCE_LENGTH]; // uint8_t signature[SIGNATURE_LENGTH]; - } unencrypted; + } data; // header + payload + nonce = must be an even number uint8_t serializedArray[1+PAYLOAD_LENGTH+NONCE_LENGTH]; }; diff --git a/board.c b/board.c index f75dc524a..572aa26c2 100644 --- a/board.c +++ b/board.c @@ -726,9 +726,10 @@ void BOARD_EEPROM_Init(void) att->band = 0xf; } } - - // 0F30..0F3F - load encryption key - EEPROM_ReadBuffer(0x0F30, gEeprom.ENC_KEY, sizeof(gEeprom.ENC_KEY)); + #ifdef ENABLE_ENCRYPTION + // 0F30..0F3F - load encryption key + EEPROM_ReadBuffer(0x0F30, gEeprom.ENC_KEY, sizeof(gEeprom.ENC_KEY)); + #endif #ifdef ENABLE_SPECTRUM_SHOW_CHANNEL_NAME BOARD_gMR_LoadChannels(); From db6b56ae7cd152ae6bb75d48657a748ed8957676 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 13:48:11 +0100 Subject: [PATCH 38/39] fix #68 red led on tx, mic mute during tx --- app/messenger.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/messenger.c b/app/messenger.c index 5fbd50477..73669dbcf 100644 --- a/app/messenger.c +++ b/app/messenger.c @@ -74,6 +74,11 @@ void MSG_FSKSendData() { // set the FM deviation level const uint16_t dev_val = BK4819_ReadRegister(BK4819_REG_40); + + // mute the mic + const uint16_t reg30 = BK4819_ReadRegister(BK4819_REG_30); + BK4819_WriteRegister(BK4819_REG_30, reg30 & ~(1u << 2)); + //UART_printf("\n BANDWIDTH : 0x%.4X", dev_val); { uint16_t deviation = 850; @@ -301,6 +306,9 @@ void MSG_FSKSendData() { // restore FM deviation level BK4819_WriteRegister(BK4819_REG_40, dev_val); + //restore mic mute + BK4819_WriteRegister(BK4819_REG_30, reg30); + // restore TX/RX filtering BK4819_WriteRegister(BK4819_REG_2B, filt_val); @@ -547,6 +555,7 @@ void MSG_SendPacket(union DataPacket packet) { msgStatus = SENDING; RADIO_SetVfoState(VFO_STATE_NORMAL); + BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false); BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, true); memset(dataPacket.serializedArray, 0, sizeof(dataPacket.serializedArray)); From bf94da37199fd8ad37a7ae1ed40851b09628ff37 Mon Sep 17 00:00:00 2001 From: Nunu Date: Fri, 26 Jan 2024 16:32:58 +0100 Subject: [PATCH 39/39] Update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d5be10dc9..339e9bbb0 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This repository is a fork of [Egzumer firmare](https://github.com/egzumer/uv-k5-firmware-custom) plus my changes: * `ENABLE_SPECTRUM_CHANNEL_SCAN` this enables spectrum channel scan mode (enter by going into memory mode and press F+5, this allows SUPER fast channel scanning (**4.5x faster than regular scanning**), regular scan of 200 memory channels takes roughly 18 seconds, **spectrum memory scan takes roughly 4 seconds**, if you have less channels stored i.e 50 - the spectrum memory scan will take only **1 second** +* `ENABLE_ENCRYPTION` - ChaCha20 256 bit encryption for the messenger, more info at [Encryption](https://github.com/kamilsss655/uv-k5-firmware-custom/wiki/44-%E2%80%90-Encryption#details) * Fixed AM AGC so **AM demodulation is crystal clear**, no audible clicks, no need for `AM_FIX`. * `RxOff` menu setting offsets the receive frequency by any specified amount in the range of `0-150Mhz` for use with upconverters. Allows to fine tune frequency (in `1kHz` steps) as opposed to other implementations that use hardcoded offsets. (**IMPORTANT: Make sure you set this value to 0 if not using an upconverter, when used for the first time. Otherwise it might load some random offset from EEPROM.**) * `ENABLE_SPECTRUM_COPY_VFO` allowing to exit the spectrum and fine tuning screen with PTT button and copy current peak frequency, modulation, step, bandwidth to VFO. Also entering spectrum will carry these settings from VFO (full integration). Now to enter fine tuning screen in spectrum press MENU button. This allows you to save and respond to the frequencies found much faster. @@ -155,6 +156,7 @@ ENABLE_SPECTRUM_SHOW_CHANNEL_NAME := 1 shows channel number and channel n ENABLE_ULTRA_LOW_POWER_TX := 0 transmits with ultra low power. useful for dev/testing ENABLE_ADJUSTABLE_RX_GAIN_SETTINGS := 1 keeps the rx gain settings set in spectrum mode after exit (otherwise these are always overwritten to default value), this makes much more sense considering that we have a radio with user adjustable gain so why not use it to adjust to current radio conditions, maximum gain allows to greatly increase reception in scan memory channels mode (in this configuration default gain settings are only set at boot and when exiting AM modulation mode to set it to sane value after am fix) ENABLE_SPECTRUM_CHANNEL_SCAN := 1 this enables spectrum channel scan mode (enter by going into memory mode and press F+5, this allows SUPER fast channel scanning (4.5x faster than regular scanning), regular scan of 200 memory channels takes roughly 18 seconds, spectrum memory scan takes roughly 4 seconds, if you have less channels stored i.e 50 - the spectrum memory scan will take only **1 second** +ENABLE_ENCRYPTION := 1 enable ChaCha20 256 bit encryption for messenger ```