diff --git a/.gitignore b/.gitignore index d1ad3d8..c593a2d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ *.wav *.bin *.sym -*.patch \ No newline at end of file +*.patch +*.txt \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index ad403d3..87dbdf2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "micro-ecc"] path = micro-ecc url = https://github.com/kmackay/micro-ecc +[submodule "tinier-aes"] + path = tinier-aes + url = https://github.com/lwvmobile/tinier-aes diff --git a/README.md b/README.md index f2c96fd..2403ba9 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,9 @@ Packet encoding is available with `m17-packet-encoder`. Its input parameters are -S - source callsign (uppercase alphanumeric string) max. 9 characters -D - destination callsign (uppercase alphanumeric string) or ALL for broadcast -C - Channel Access Number (0..15, default - 0) --n - number of bytes (1 to 798) +-T - SMS Text Message (example: -T 'Hello World! This is a text message') +-R - Raw Hex Octets (example: -R 010203040506070809) +-n - number of bytes, only when pre-encoded data passed over stdin (1 to 798) -o - output file path/name -x - binary output (M17 baseband as a packed bitstream) -r - raw audio output - default (single channel, signed 16-bit LE, +7168 for the +1.0 symbol, 10 samples per symbol) @@ -87,7 +89,7 @@ Packet encoding is available with `m17-packet-encoder`. Its input parameters are -w - libsndfile wav audio output - default (single channel, signed 16-bit LE, +7168 for the +1.0 symbol, 10 samples per symbol) ``` -Input data is passed over stdin. Example command: +Input data can be pre-encoded and passed over stdin. Example command: `echo -en "\x05Testing M17 packet mode.\x00" | ./m17-packet-encode -S N0CALL -D ALL -C 0 -n 26 -f -o baseband.sym` @@ -97,16 +99,16 @@ Input data is passed over stdin. Example command: Output: ``` -DST: ALL FFFFFFFFFFFF -SRC: N0CALL 00004B13D106 -Data CRC: BFEC -LSF CRC: 432A +SMS: Testing M17 packet mode. +DST: ALL FFFFFFFFFFFF +SRC: N0CALL 00004B13D106 +CAN: 00 +Data CRC: BFEC +LSF CRC: 432A FN:00 (full frame) -0554657374696E67204D3137207061636B6574206D6F64652E00 FN:-- (ending frame) -00BFEC0000000000000000000000000000000000000000000084 -FULL: 0554657374696E67204D3137207061636B6574206D6F64652E00BFEC - SMS: Testing M17 packet mode. +PKT: 05 54 65 73 74 69 6E 67 20 4D 31 37 20 70 61 63 6B 65 74 20 6D 6F 64 65 2E + 00 BF EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ``` Decode packet created with above sample: @@ -117,26 +119,28 @@ Output: ``` DST: FFFFFFFFFFFF SRC: 00004B13D106 TYPE: 0002 META: 0000000000000000000000000000 LSF_CRC_OK -Testing M17 packet mode. +SMS: Testing M17 packet mode. ``` -Encode directly as wav format (skip sox): +Encode User Text String as wav format: -`echo -en "\x05Testing M17 packet mode.\x00" | ./m17-packet-encode -S N0CALL -D AB1CDE -C 7 -n 26 -w -o baseband.wav` +`./m17-packet-encode -S N0CALL -D AB1CDE -T 'This is an SMS Text Message Generated by m17-packet-encode' -C 7 -w -o baseband.wav` Output: ``` -DST: AB1CDE 00001F245D51 -SRC: N0CALL 00004B13D106 -Data CRC: BFEC -LSF CRC: F754 +SMS: This is an SMS Text Message Generated by m17-packet-encode +DST: AB1CDE 00001F245D51 +SRC: N0CALL 00004B13D106 +CAN: 07 +Data CRC: FD81 +LSF CRC: F754 FN:00 (full frame) -0554657374696E67204D3137207061636B6574206D6F64652E00 +FN:01 (full frame) FN:-- (ending frame) -00BFEC0000000000000000000000000000000000000000000084 -FULL: 0554657374696E67204D3137207061636B6574206D6F64652E00BFEC - SMS: Testing M17 packet mode. +PKT: 05 54 68 69 73 20 69 73 20 61 6E 20 53 4D 53 20 54 65 78 74 20 4D 65 73 73 + 61 67 65 20 47 65 6E 65 72 61 74 65 64 20 62 79 20 6D 31 37 2D 70 61 63 6B + 65 74 2D 65 6E 63 6F 64 65 00 FD 81 00 00 00 00 00 00 00 00 00 00 00 00 00 ``` Decode with M17-FME: @@ -147,26 +151,55 @@ Output: ``` M17 Project - Florida Man Edition -Build Version: 2024-1-g4f2c15c -Session Number: A4F5 +Build Version: 2024-10-g060dd21 +Session Number: A751 M17 Project RF Audio Frame Demodulator. SNDFile (.wav, .rrc) Input File: baseband.wav Payload Verbosity: 1; -M17 LSF Frame Sync (08:57:09): - DST: AB1CDE SRC: N0CALL CAN: 7; Data Packet +M17 LSF Frame Sync (16:12:34): + DST: AB1CDE SRC: N0CALL CAN: 7; Data Packet FT: 0382; ET: 0; ES: 0; LSF: 00 00 1F 24 5D 51 00 00 4B 13 D1 06 03 82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F7 54 (CRC CHK) E: F754; C: F754; -M17 PKT Frame Sync (08:57:09): CNT: 00; PBC: 00; EOT: 0; - pkt: 0554657374696E67204D3137207061636B6574206D6F64652E00 -M17 PKT Frame Sync (08:57:09): CNT: 01; LST: 01; EOT: 1; - pkt: 00BFEC0000000000000000000000000000000000000000000084 Protocol: SMS; - SMS: Testing M17 packet mode. - PKT: 05 54 65 73 74 69 6E 67 20 4D 31 37 20 70 61 63 6B 65 74 20 6D 6F 64 65 2E - 00 BF EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - (CRC CHK) E: BFEC; C: BFEC; -M17 No Frame Sync (08:57:09): +M17 PKT Frame Sync (16:12:34): CNT: 00; PBC: 00; EOT: 0; + pkt: 055468697320697320616E20534D532054657874204D65737300 +M17 PKT Frame Sync (16:12:34): CNT: 01; PBC: 01; EOT: 0; + pkt: 6167652047656E657261746564206279206D31372D7061636B04 +M17 PKT Frame Sync (16:12:34): CNT: 02; LST: 12; EOT: 1; + pkt: 65742D656E636F646500FD8100000000000000000000000000B0 Protocol: SMS; + SMS: This is an SMS Text Message Generated by m17-packet-encode + PKT: 05 54 68 69 73 20 69 73 20 61 6E 20 53 4D 53 20 54 65 78 74 20 4D 65 73 73 + 61 67 65 20 47 65 6E 65 72 61 74 65 64 20 62 79 20 6D 31 37 2D 70 61 63 6B + 65 74 2D 65 6E 63 6F 64 65 00 FD 81 00 00 00 00 00 00 00 00 00 00 00 00 00 + (CRC CHK) E: FD81; C: FD81; +``` +Encode Raw Hex Octet String as float symbol format: + +`./m17-packet-encode -S N0CALL -D AB1CDE -R 010203040506070809 -C 7 -f -o float.sym` + +Output: + +``` +Raw Len: 9; Raw Octets: 01 02 03 04 05 06 07 08 09 +DST: AB1CDE 00001F245D51 +SRC: N0CALL 00004B13D106 +CAN: 07 +Data CRC: D7CE +LSF CRC: F754 +FN:-- (ending frame) +PKT: 01 02 03 04 05 06 07 08 09 D7 CE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +``` + +Decode (rolling) packets created with above sample: + +`tail -f float.sym | ./m17-packet-decode` + +Output: + +``` +DST: 00001F245D51 SRC: 00004B13D106 TYPE: 0382 META: 0000000000000000000000000000 LSF_CRC_OK +PKT: 01 02 03 04 05 06 07 08 09 D7 CE ``` \ No newline at end of file diff --git a/SP5WWP/m17-coder/Makefile b/SP5WWP/m17-coder/Makefile index 7825849..6bb1d07 100644 --- a/SP5WWP/m17-coder/Makefile +++ b/SP5WWP/m17-coder/Makefile @@ -1,5 +1,5 @@ m17-coder-sym: m17-coder-sym.c - gcc -O2 -Wall -Wextra m17-coder-sym.c ../../micro-ecc/uECC.c -o m17-coder-sym -lm -lm17 + gcc -O2 -Wall -Wextra m17-coder-sym.c ../../micro-ecc/uECC.c ../../tinier-aes/aes.c -o m17-coder-sym -lm -lm17 clean: rm -f m17-coder-sym diff --git a/SP5WWP/m17-coder/m17-coder-sym.c b/SP5WWP/m17-coder/m17-coder-sym.c index e5bf3b2..88cb38c 100644 --- a/SP5WWP/m17-coder/m17-coder-sym.c +++ b/SP5WWP/m17-coder/m17-coder-sym.c @@ -2,14 +2,29 @@ #include #include #include +#include //libm17 #include "../../libm17/m17.h" //micro-ecc #include "../../micro-ecc/uECC.h" +//tinier-aes +#include "../../tinier-aes/aes.h" //#define FN60_DEBUG +//TODO: Load Signature Private and Public Keys from file +//TODO: More Thorough Testing to make sure everything is good +//TODO: Round of Cleanup (and test after cleaning up! +//TODO: OR Frametype Bits depending on encryption type, subtype, and signed sig + +//Wishlist: Please Woj, can we use the subtype on AES to signal AES 128, AES 192, or AES 256? +// We already to it for Scrambler + +//Wishlist: way to fix this warning without changing uECC source code or disabling -Wall -Wextra +//../../micro-ecc/curve-specific.inc:544:59: warning: unused parameter ‘curve’ [-Wunused-parameter] +//544 | static void mod_sqrt_secp224r1(uECC_word_t *a, uECC_Curve curve) { + struct LSF lsf, next_lsf; uint8_t lich[6]; //48 bits packed raw, unencoded LICH @@ -29,15 +44,100 @@ uint8_t finished=0; //no more data at stdin? //used for signatures uint8_t digest[16]={0}; //16-byte field for the stream digest uint8_t signed_str=0; //is the stream supposed to be signed? +uint8_t priv_key_loaded=0; //do we have a sig key loaded? uint8_t priv_key[32]={0}; //private key uint8_t sig[64]={0}; //ECDSA signature int dummy=0; //dummy var to make compiler quieter +//AES +uint8_t encryption=0; +int aes_type = 1; //1=AES128, 2=AES192, 3=AES256 +uint8_t key[32]; +uint8_t iv[16]; +time_t epoch = 1577836800L; //Jan 1, 2020, 00:00:00 UTC + +//Scrambler +uint8_t scr_bytes[16]; +uint8_t scrambler_pn[128]; +uint32_t scrambler_seed=0; +int8_t scrambler_subtype = -1; + +//debug mode (preset lsf, type, zero payload for enc testing, etc) +uint8_t debug_mode=0; + +//scrambler pn sequence generation +void scrambler_sequence_generator() +{ + int i = 0; + uint32_t lfsr, bit; + lfsr = scrambler_seed; + + //only set if not initially set (first run), it is possible (and observed) that the scrambler_subtype can + //change on subsequent passes if the current SEED for the LFSR falls below one of these thresholds + if (scrambler_subtype == -1) + { + if (lfsr > 0 && lfsr <= 0xFF) scrambler_subtype = 0; // 8-bit key + else if (lfsr > 0xFF && lfsr <= 0xFFFF) scrambler_subtype = 1; //16-bit key + else if (lfsr > 0xFFFF && lfsr <= 0xFFFFFF) scrambler_subtype = 2; //24-bit key + else scrambler_subtype = 0; // 8-bit key (default) + } + + //TODO: Set Frame Type based on scrambler_subtype value + if (debug_mode > 1) + { + fprintf (stderr, "\nScrambler Key: 0x%06X; Seed: 0x%06X; Subtype: %02d;", scrambler_seed, lfsr, scrambler_subtype); + fprintf (stderr, "\n pN: "); + } + + //run pN sequence with taps specified + for (i = 0; i < 128; i++) + { + //get feedback bit with specified taps, depending on the scrambler_subtype + if (scrambler_subtype == 0) + bit = (lfsr >> 7) ^ (lfsr >> 5) ^ (lfsr >> 4) ^ (lfsr >> 3); + else if (scrambler_subtype == 1) + bit = (lfsr >> 15) ^ (lfsr >> 14) ^ (lfsr >> 12) ^ (lfsr >> 3); + else if (scrambler_subtype == 2) + bit = (lfsr >> 23) ^ (lfsr >> 22) ^ (lfsr >> 21) ^ (lfsr >> 16); + else bit = 0; //should never get here, but just in case + + bit &= 1; //truncate bit to 1 bit (required since I didn't do it above) + lfsr = (lfsr << 1) | bit; //shift LFSR left once and OR bit onto LFSR's LSB + lfsr &= 0xFFFFFF; //truncate lfsr to 24-bit (really doesn't matter) + scrambler_pn[i] = bit; + + } + + //pack bit array into byte array for easy data XOR + pack_bit_array_into_byte_array(scrambler_pn, scr_bytes, 16); + + //save scrambler seed for next round + scrambler_seed = lfsr; + + //truncate seed so subtype will continue to set properly on subsequent passes + if (scrambler_subtype == 0) scrambler_seed &= 0xFF; + if (scrambler_subtype == 1) scrambler_seed &= 0xFFFF; + if (scrambler_subtype == 2) scrambler_seed &= 0xFFFFFF; + else scrambler_seed &= 0xFF; + + if (debug_mode > 1) + { + //debug packed bytes + for (i = 0; i < 16; i++) + fprintf (stderr, " %02X", scr_bytes[i]); + fprintf (stderr, "\n"); + } + +} + void usage(void) { fprintf(stderr, "Usage:\n"); fprintf(stderr, "-s - Private key for ECDSA signature, 32 bytes (-s [hex_string|key_file]),\n"); + fprintf(stderr, "-K - AES encryption key (-K [hex_string|text_file]),\n"); + fprintf(stderr, "-k - Scrambler encryption seed value (-k [hex_string]),\n"); + fprintf(stderr, "-D - Debug mode,\n"); fprintf(stderr, "-h - help / print usage\n"); } @@ -100,6 +200,10 @@ void parse_raw_key_string(uint8_t* dest, const char* inp) //main routine int main(int argc, char* argv[]) { + srand(time(NULL)); //random number generator (for IV rand() seed value) + memset(key, 0, 32*sizeof(uint8_t)); + memset(iv, 0, 16*sizeof(uint8_t)); + //scan command line options for input data (purely optional) if(argc>=1) { @@ -118,9 +222,123 @@ int main(int argc, char* argv[]) } parse_raw_key_string(priv_key, argv[i+1]); - + priv_key_loaded=1; //mainly for debug mode i++; } + else if(argv[i][1]=='K') //-K - AES Encryption + { + if(strstr(argv[i+1], ".")) //if the next arg contains a dot - read key from a text file + { + char fname[128]={'\0'}; //output file + if(strlen(&argv[i+1][0])>0) + memcpy(fname, &argv[i+1][0], strlen(argv[i+1])); + else + { + fprintf(stderr, "Invalid filename. Exiting...\n"); + return -1; + } + + FILE* fp; + char source_str[64]; + + fp = fopen(fname, "r"); + if(!fp) + { + fprintf(stderr, "Failed to load file %s.\n", fname); + return -1; + } + + //size check + size_t len = fread(source_str, 1, 64, fp); //TODO: check length + fclose(fp); + + if(len==256/4) + fprintf(stderr, "AES256"); + else if(len==192/4) + fprintf(stderr, "AES192"); + else if(len==128/4) + fprintf(stderr, "AES128"); + else + { + fprintf(stderr, "Invalid key length.\n"); + return -1; + } + + parse_raw_key_string(key, source_str); + + fprintf(stderr, " key:"); + for(uint8_t i=0; i24/4) //24-bit is the largest seed value + { + fprintf(stderr, "Invalid key length.\n"); + return -1; + } + + parse_raw_key_string(key, argv[i+1]); + scrambler_seed = (key[0] << 16) | (key[1] << 8) | (key[2] << 0); + + if(length<=2) + { + scrambler_seed = scrambler_seed >> 16; + fprintf(stderr, "Scrambler key: 0x%02X (8-bit)\n", scrambler_seed); + } + else if(length<=4) + { + scrambler_seed = scrambler_seed >> 8; + fprintf(stderr, "Scrambler key: 0x%04X (16-bit)\n", scrambler_seed); + } + else + fprintf(stderr, "Scrambler key: 0x%06X (24-bit)\n", scrambler_seed); + + encryption=1; //Scrambler key was passed + } + else if(argv[i][1]=='D') //-D - Debug Mode + { + debug_mode=1; + } + else if(argv[i][1]=='h') //-h - help / usage { usage(); @@ -136,6 +354,14 @@ int main(int argc, char* argv[]) } } + if(encryption==2) + { + for(uint8_t i=0; i<4; i++) + iv[i] = ((uint32_t)(time(NULL)&0xFFFFFFFF)-(uint32_t)epoch) >> (24-(i*8)); + for(uint8_t i=3; i<14; i++) + iv[i] = rand() & 0xFF; //10 random bytes + } + const struct uECC_Curve_t* curve = uECC_secp256r1(); //send out the preamble @@ -143,12 +369,76 @@ int main(int argc, char* argv[]) send_preamble(frame_buff, &frame_buff_cnt, 0); //0 - LSF preamble, as opposed to 1 - BERT preamble fwrite((uint8_t*)frame_buff, SYM_PER_FRA*sizeof(float), 1, stdout); - //read data - dummy=fread(&(lsf.dst), 6, 1, stdin); - dummy=fread(&(lsf.src), 6, 1, stdin); - dummy=fread(&(lsf.type), 2, 1, stdin); - dummy=fread(&(lsf.meta), 14, 1, stdin); - dummy=fread(data, 16, 1, stdin); + if (debug_mode == 1) + { + //broadcast + memset(lsf.dst, 0xFF, 6*sizeof(uint8_t)); + + //N0CALL + lsf.src[0] = 0x00; + lsf.src[1] = 0x00; + lsf.src[2] = 0x4B; + lsf.src[3] = 0x13; + lsf.src[4] = 0xD1; + lsf.src[5] = 0x06; + + if (encryption == 2) //AES ENC, 3200 voice + { + lsf.type[0] = 0x03; + lsf.type[1] = 0x95; + } + else if (encryption == 1) //Scrambler ENC, 3200 Voice + { + lsf.type[0] = 0x03; + lsf.type[1] = 0xCD; + } + else //no enc or subtype field, normal 3200 voice + { + lsf.type[0] = 0x00; + lsf.type[1] = 0x05; + } + + //a signature key is loaded, OR this bit + if(priv_key_loaded) + { + signed_str = 1; + lsf.type[0] |= 0x8; + } + + //calculate LSF CRC (unclear whether or not this is only + //needed here for debug, or if this is missing on every initial LSF) + uint16_t ccrc=LSF_CRC(&lsf); + lsf.crc[0]=ccrc>>8; + lsf.crc[1]=ccrc&0xFF; + + finished = 0; + + //debug sig with random payloads (don't play the audio) + for (uint8_t i = 0; i < 16; i++) + data[i] = 0x69; //rand() & 0xFF; + } + else + { + //read data + dummy=fread(&(lsf.dst), 6, 1, stdin); + dummy=fread(&(lsf.src), 6, 1, stdin); + dummy=fread(&(lsf.type), 2, 1, stdin); + dummy=fread(&(lsf.meta), 14, 1, stdin); + dummy=fread(data, 16, 1, stdin); + } + + //AES encryption enabled - use 112 bits of IV + if(encryption==2) + { + memcpy(&(lsf.meta), iv, 14); + iv[14] = (fn >> 8) & 0x7F; + iv[15] = (fn >> 0) & 0xFF; + + //re-calculate LSF CRC with IV insertion + uint16_t ccrc=LSF_CRC(&lsf); + lsf.crc[0]=ccrc>>8; + lsf.crc[1]=ccrc&0xFF; + } while(!finished) { @@ -183,12 +473,79 @@ int main(int argc, char* argv[]) got_lsf=1; } - //check if theres any more data - if(fread(&(next_lsf.dst), 6, 1, stdin)<1) finished=1; - if(fread(&(next_lsf.src), 6, 1, stdin)<1) finished=1; - if(fread(&(next_lsf.type), 2, 1, stdin)<1) finished=1; - if(fread(&(next_lsf.meta), 14, 1, stdin)<1) finished=1; - if(fread(next_data, 16, 1, stdin)<1) finished=1; + if (debug_mode == 1) + { + //broadcast + memset(next_lsf.dst, 0xFF, 6*sizeof(uint8_t)); + + //N0CALL + next_lsf.src[0] = 0x00; + next_lsf.src[1] = 0x00; + next_lsf.src[2] = 0x4B; + next_lsf.src[3] = 0x13; + next_lsf.src[4] = 0xD1; + next_lsf.src[5] = 0x06; + + if (encryption == 2) //AES ENC, 3200 voice + { + next_lsf.type[0] = 0x03; + next_lsf.type[1] = 0x95; + } + else if (encryption == 1) //Scrambler ENC, 3200 Voice + { + next_lsf.type[0] = 0x03; + next_lsf.type[1] = 0xCD; + } + else //no enc or subtype field, normal 3200 voice + { + next_lsf.type[0] = 0x00; + next_lsf.type[1] = 0x05; + } + + //a signature key is loaded, OR this bit + if(priv_key_loaded) + next_lsf.type[0] |= 0x8; + + finished = 0; + + memset(next_data, 0, sizeof(next_data)); + memcpy(data, next_data, sizeof(data)); + if (fn == 60) + finished = 1; + + //debug sig with random payloads (don't play the audio) + for (uint8_t i = 0; i < 16; i++) + data[i] = 0x69; //rand() & 0xFF; + + } + else + { + //check if theres any more data + if(fread(&(next_lsf.dst), 6, 1, stdin)<1) finished=1; + if(fread(&(next_lsf.src), 6, 1, stdin)<1) finished=1; + if(fread(&(next_lsf.type), 2, 1, stdin)<1) finished=1; + if(fread(&(next_lsf.meta), 14, 1, stdin)<1) finished=1; + if(fread(next_data, 16, 1, stdin)<1) finished=1; + } + + //AES + if(encryption==2) + { + memcpy(&(next_lsf.meta), iv, 14); + iv[14] = (fn >> 8) & 0x7F; + iv[15] = (fn >> 0) & 0xFF; + aes_ctr_bytewise_payload_crypt(iv, key, data, aes_type); + } + + //Scrambler + else if (encryption == 1) + { + scrambler_sequence_generator(); + for(uint8_t i=0; i<16; i++) + { + data[i] ^= scr_bytes[i]; + } + } if(!finished) { @@ -312,6 +669,19 @@ int main(int argc, char* argv[]) for(uint8_t i=0; i scr.sym +//decode debug with -- m17-fme -r -f scr.sym -v 1 -e 123456 + +//AES (with file import) +//encode debug with -- ./m17-coder-sym -D -K sample_aes_key.txt> float.sym +//decode debug with -- m17-fme -r -f float.sym -v 1 -J sample_aes_key.txt + +//Signatures +//encode debug with -- ./m17-coder-sym -D -s 69b07d7afe7f843e56ecbf536a49461dc5901c975d895bf1649cabff8f9b208b > float.sym +//decode debug with -- cat ../m17-coder/float.sym | ./m17-decoder-sym -s c6c03dd11276aa917e7d83ae16d7f4fbf06f31be5869f9ae8004c329947dc4eeef0d9363653c8edf93e50912c6c515b40e0a8cbeea5e984dbc78e1993c8fbd5d +//decode debug with -- m17-fme -r -f float.sym -v 1 -k ../m17-decoder/sample_pub_key.txt + +//Signatures and AES +//encode debug with -- ./m17-coder-sym -D -K sample_aes_key.txt -s 69b07d7afe7f843e56ecbf536a49461dc5901c975d895bf1649cabff8f9b208b > float.sym +//decode debug with -- cat ../m17-coder/float.sym | ./m17-decoder-sym -s c6c03dd11276aa917e7d83ae16d7f4fbf06f31be5869f9ae8004c329947dc4eeef0d9363653c8edf93e50912c6c515b40e0a8cbeea5e984dbc78e1993c8fbd5d -K sample_aes_key.txt +//decode debug with -- m17-fme -r -f float.sym -v 1 -k ../m17-decoder/sample_pub_key.txt -J sample_aes_key.txt + +//Signatures and 24-bit Scrambler +//encode debug with -- ./m17-coder-sym -D -k 543210 -s 69b07d7afe7f843e56ecbf536a49461dc5901c975d895bf1649cabff8f9b208b > float.sym +//decode debug with -- cat ../m17-coder/float.sym | ./m17-decoder-sym -s c6c03dd11276aa917e7d83ae16d7f4fbf06f31be5869f9ae8004c329947dc4eeef0d9363653c8edf93e50912c6c515b40e0a8cbeea5e984dbc78e1993c8fbd5d -k 543210 +//decode debug with -- m17-fme -r -f float.sym -v 1 -k ../m17-decoder/sample_pub_key.txt -e 543210 diff --git a/SP5WWP/m17-coder/sample_aes_key.txt b/SP5WWP/m17-coder/sample_aes_key.txt new file mode 100644 index 0000000..b010a83 --- /dev/null +++ b/SP5WWP/m17-coder/sample_aes_key.txt @@ -0,0 +1 @@ +1234567890ABCDEF7777777777777777FEDCBA09876543218888888888888888 \ No newline at end of file diff --git a/SP5WWP/m17-decoder/Makefile b/SP5WWP/m17-decoder/Makefile index fc02191..9b45cb6 100644 --- a/SP5WWP/m17-decoder/Makefile +++ b/SP5WWP/m17-decoder/Makefile @@ -1,5 +1,5 @@ m17-decoder-sym: m17-decoder-sym.c - gcc -O2 -Wall -Wextra m17-decoder-sym.c ../../micro-ecc/uECC.c -o m17-decoder-sym -lm -lm17 + gcc -O2 -Wall -Wextra m17-decoder-sym.c ../../micro-ecc/uECC.c ../../tinier-aes/aes.c -o m17-decoder-sym -lm -lm17 install: sudo cp m17-decoder-sym /usr/local/bin diff --git a/SP5WWP/m17-decoder/m17-decoder-sym.c b/SP5WWP/m17-decoder/m17-decoder-sym.c index 933cf44..7660803 100644 --- a/SP5WWP/m17-decoder/m17-decoder-sym.c +++ b/SP5WWP/m17-decoder/m17-decoder-sym.c @@ -7,6 +7,10 @@ #include "../../libm17/m17.h" //micro-ecc #include "../../micro-ecc/uECC.h" +//tinier-aes +#include "../../tinier-aes/aes.h" + +//TODO: Load Signature Private and Public Keys from file //settings uint8_t decode_callsigns=0; @@ -42,6 +46,126 @@ uint8_t signed_str=0; //is the stream signed? uint8_t pub_key[64]={0}; //public key uint8_t sig[64]={0}; //ECDSA signature +//AES +uint8_t encryption=0; +int aes_type = 1; //1=AES128, 2=AES192, 3=AES256 +uint8_t key[32]; +uint8_t iv[16]; +time_t epoch = 1577836800L; //Jan 1, 2020, 00:00:00 UTC + +//Scrambler +uint8_t scr_bytes[16]; +uint8_t scrambler_pn[128]; +uint32_t scrambler_key=0; //keep set to initial value for seed calculation function +uint32_t scrambler_seed=0; +int8_t scrambler_subtype = -1; + +//debug mode +uint8_t debug_mode=0; //TODO: Remove lines looking at this + +//this is generating a correct seed value based on the fn value, +//ideally, we would only want to run this under poor signal, frame skips, etc +//Note: Running this every frame will lag if high fn values (observed with test file) +uint32_t scrambler_seed_calculation(int8_t subtype, uint32_t key, int fn) +{ + int i; + uint32_t lfsr, bit; + + lfsr = key; bit = 0; + for (i = 0; i < 128*fn; i++) + { + //get feedback bit with specified taps, depending on the subtype + if (subtype == 0) + bit = (lfsr >> 7) ^ (lfsr >> 5) ^ (lfsr >> 4) ^ (lfsr >> 3); + else if (subtype == 1) + bit = (lfsr >> 15) ^ (lfsr >> 14) ^ (lfsr >> 12) ^ (lfsr >> 3); + else if (subtype == 2) + bit = (lfsr >> 23) ^ (lfsr >> 22) ^ (lfsr >> 21) ^ (lfsr >> 16); + else bit = 0; //should never get here, but just in case + + bit &= 1; //truncate bit to 1 bit + lfsr = (lfsr << 1) | bit; //shift LFSR left once and OR bit onto LFSR's LSB + lfsr &= 0xFFFFFF; //truncate lfsr to 24-bit + + } + + //truncate seed so subtype will continue to set properly on subsequent passes + if (scrambler_subtype == 0) scrambler_seed &= 0xFF; + if (scrambler_subtype == 1) scrambler_seed &= 0xFFFF; + if (scrambler_subtype == 2) scrambler_seed &= 0xFFFFFF; + else scrambler_seed &= 0xFF; + + //debug + //fprintf (stderr, "\nScrambler Key: 0x%06X; Seed: 0x%06X; Subtype: %02d; FN: %05d; ", key, lfsr, subtype, fn); + + return lfsr; +} + +//scrambler pn sequence generation +void scrambler_sequence_generator() +{ + int i = 0; + uint32_t lfsr, bit; + lfsr = scrambler_seed; + + //only set if not initially set (first run), it is possible (and observed) that the scrambler_subtype can + //change on subsequent passes if the current SEED for the LFSR falls below one of these thresholds + if (scrambler_subtype == -1) + { + if (lfsr > 0 && lfsr <= 0xFF) scrambler_subtype = 0; // 8-bit key + else if (lfsr > 0xFF && lfsr <= 0xFFFF) scrambler_subtype = 1; //16-bit key + else if (lfsr > 0xFFFF && lfsr <= 0xFFFFFF) scrambler_subtype = 2; //24-bit key + else scrambler_subtype = 0; // 8-bit key (default) + } + + //TODO: Set Frame Type based on scrambler_subtype value + if (debug_mode > 1) + { + fprintf (stderr, "\nScrambler Key: 0x%06X; Seed: 0x%06X; Subtype: %02d;", scrambler_seed, lfsr, scrambler_subtype); + fprintf (stderr, "\n pN: "); + } + + //run pN sequence with taps specified + for (i = 0; i < 128; i++) + { + //get feedback bit with specified taps, depending on the scrambler_subtype + if (scrambler_subtype == 0) + bit = (lfsr >> 7) ^ (lfsr >> 5) ^ (lfsr >> 4) ^ (lfsr >> 3); + else if (scrambler_subtype == 1) + bit = (lfsr >> 15) ^ (lfsr >> 14) ^ (lfsr >> 12) ^ (lfsr >> 3); + else if (scrambler_subtype == 2) + bit = (lfsr >> 23) ^ (lfsr >> 22) ^ (lfsr >> 21) ^ (lfsr >> 16); + else bit = 0; //should never get here, but just in case + + bit &= 1; //truncate bit to 1 bit (required since I didn't do it above) + lfsr = (lfsr << 1) | bit; //shift LFSR left once and OR bit onto LFSR's LSB + lfsr &= 0xFFFFFF; //truncate lfsr to 24-bit (really doesn't matter) + scrambler_pn[i] = bit; + + } + + //pack bit array into byte array for easy data XOR + pack_bit_array_into_byte_array(scrambler_pn, scr_bytes, 16); + + //save scrambler seed for next round + scrambler_seed = lfsr; + + //truncate seed so subtype will continue to set properly on subsequent passes + if (scrambler_subtype == 0) scrambler_seed &= 0xFF; + if (scrambler_subtype == 1) scrambler_seed &= 0xFFFF; + if (scrambler_subtype == 2) scrambler_seed &= 0xFFFFFF; + else scrambler_seed &= 0xFF; + + if (debug_mode > 1) + { + //debug packed bytes + for (i = 0; i < 16; i++) + fprintf (stderr, " %02X", scr_bytes[i]); + fprintf (stderr, "\n"); + } + +} + void usage(void) { fprintf(stderr, "Usage:\n"); @@ -51,6 +175,8 @@ void usage(void) fprintf(stderr, "-l - Display LSF CRC checks,\n"); fprintf(stderr, "-d - Set syncword detection threshold (decimal value),\n"); fprintf(stderr, "-s - Public key for ECDSA signature, 64 bytes (-s [hex_string|key_file]),\n"); + fprintf(stderr, "-K - AES encryption key (-K [hex_string|text_file]),\n"); + fprintf(stderr, "-k - Scrambler encryption seed value (-k [hex_string]),\n"); fprintf(stderr, "-h - help / print usage\n"); } @@ -161,6 +287,121 @@ int main(int argc, char* argv[]) i++; } + + //Woj: There is a bug in loading keys this way, I think + // it is to do with how scannign args is different here than in the encoder? + //when loading by file, the key is shifted by one char + //when loading the hex value, though, it works fine. + if(argv[i][1]=='K') //-K - AES Encryption + { + if(strstr(argv[i+1], ".")) //if the next arg contains a dot - read key from a text file + { + char fname[128]={'\0'}; //output file + if(strlen(&argv[i+1][0])>0) + memcpy(fname, &argv[i+1][0], strlen(argv[i+1])); + else + { + fprintf(stderr, "Invalid filename. Exiting...\n"); + return -1; + } + + FILE* fp; + char source_str[64]; + + fp = fopen(fname, "r"); + if(!fp) + { + fprintf(stderr, "Failed to load file %s.\n", fname); + return -1; + } + + //size check + size_t len = fread(source_str, 1, 64, fp); //TODO: check length + fclose(fp); + + if(len==256/4) + fprintf(stderr, "AES256"); + else if(len==192/4) + fprintf(stderr, "AES192"); + else if(len==128/4) + fprintf(stderr, "AES128"); + else + { + fprintf(stderr, "Invalid key length.\n"); + return -1; + } + + parse_raw_key_string(key, source_str); + + fprintf(stderr, " key:"); + for(uint8_t i=0; i24/4) //24-bit is the largest seed value + { + fprintf(stderr, "Invalid key length.\n"); + return -1; + } + + parse_raw_key_string(key, argv[i+1]); + scrambler_key = (key[0] << 16) | (key[1] << 8) | (key[2] << 0); + + if(length<=2) + { + scrambler_key = scrambler_key >> 16; + fprintf(stderr, "Scrambler key: 0x%02X (8-bit)\n", scrambler_key); + } + else if(length<=4) + { + scrambler_key = scrambler_key >> 8; + fprintf(stderr, "Scrambler key: 0x%04X (16-bit)\n", scrambler_key); + } + else + fprintf(stderr, "Scrambler key: 0x%06X (24-bit)\n", scrambler_key); + + encryption=1; //Scrambler key was passed + scrambler_seed = scrambler_key; //set initial seed value to key value + } if(!strcmp(argv[i], "-l")) { @@ -250,6 +491,44 @@ int main(int argc, char* argv[]) uint16_t type=(uint16_t)lsf[12]*0x100+lsf[13]; //big-endian signed_str=(type>>11)&1; + ///if the stream is signed (process before decryption) + if(signed_str && fn<0x7FFC) + { + if(fn==0) + memset(digest, 0, sizeof(digest)); + + for(uint8_t i=0; i= 0x7FFC + //The Signature is not encrypted + + //AES + if (encryption == 2 && fn<0x7FFC) + { + memcpy(iv, lsf+14, 14); + iv[14] = frame_data[1] & 0x7F; + iv[15] = frame_data[2] & 0xFF; + aes_ctr_bytewise_payload_crypt(iv, key, frame_data+3, aes_type); + } + + //Scrambler + if (encryption == 1 && fn<0x7FFC) + { + if ((fn % 0x8000)!=expected_next_fn) //frame skip, etc + scrambler_seed = scrambler_seed_calculation(scrambler_subtype, scrambler_key, fn&0x7FFF); + scrambler_sequence_generator(); + for(uint8_t i=0; i<16; i++) + { + frame_data[i+3] ^= scr_bytes[i]; + } + } + //dump data - first byte is empty printf("FN: %04X PLD: ", fn); for(uint8_t i=3; i<19; i++) @@ -264,20 +543,6 @@ int main(int argc, char* argv[]) //send codec2 stream to stdout //fwrite(&frame_data[3], 16, 1, stdout); - //if the stream is signed - if(signed_str && fn<0x7FFC) - { - if(fn==0) - memset(digest, 0, sizeof(digest)); - - for(uint8_t i=0; i 797) len = 797; + + //set num_bytes to len + 1 + num_bytes = len + 0; //doing 0 instead, let user pass an extra 00 on the end if they want it there + + char octet_char[3]; + octet_char[2] = 0; + uint16_t k = 0; + uint16_t i = 0; + + //debug + fprintf (stderr, "\nRaw Len: %d; Raw Octets:", len); + + for (i = 0; i < len; i++) + { + strncpy (octet_char, input+k, 2); + octet_char[2] = 0; + sscanf (octet_char, "%hhX", &raw[i]); + + //debug + // fprintf (stderr, " (%s)", octet_char); + fprintf (stderr, " %02X", raw[i]); + + k += 2; + } + + fprintf (stderr, "\n"); +} + //main routine int main(int argc, char* argv[]) { //scan command line options for input data - //TODO: support for strings with spaces, the code below is NOT foolproof! - //the user has to provide a minimum of 2 parameters: number of bytes and output filename + //WIP: support for text strings with spaces and raw hex octet strings (still NOT foolproof) + //the user has to provide a minimum of 2 parameters: input string or num_bytes, output type, and output filename if(argc>=4) { for(uint8_t i=1; i0) + { + memset(text, 0, 800*sizeof(char)); + memcpy(text, &argv[i+1][0], strlen(argv[i+1])); + std_encode = 0; + sms_encode = 1; + raw_encode = 0; + } + } + else if(argv[i][1]=='R') //-R - Raw Octets + { + if(strlen(&argv[i+1][0])>0) + { + memset (raw, 0, sizeof(raw)); + parse_raw_user_string (argv[i+1]); + std_encode = 0; + sms_encode = 0; + raw_encode = 1; + } + } else if(argv[i][1]=='o') //-o - output filename { if(strlen(&argv[i+1][0])>0) @@ -187,7 +262,9 @@ int main(int argc, char* argv[]) fprintf(stderr, "-S - source callsign (uppercase alphanumeric string) max. 9 characters,\n"); fprintf(stderr, "-D - destination callsign (uppercase alphanumeric string) or ALL for broadcast,\n"); fprintf(stderr, "-C - Channel Access Number (0..15, default - 0),\n"); - fprintf(stderr, "-n - number of bytes (1 to 798),\n"); + fprintf(stderr, "-T - SMS Text Message (example: -T 'Hello World! This is a text message'),\n"); + fprintf(stderr, "-R - Raw Hex Octets (example: -R 010203040506070809),\n"); + fprintf(stderr, "-n - number of bytes, only when pre-encoded data passed over stdin (1 to 798),\n"); fprintf(stderr, "-o - output file path/name,\n"); fprintf(stderr, "Output formats:\n"); //fprintf(stderr, "-x - binary output (M17 baseband as a packed bitstream),\n"); @@ -199,13 +276,8 @@ int main(int argc, char* argv[]) return -1; } - //assert number of bytes and filename - if(num_bytes==0) - { - fprintf(stderr, "Number of bytes not set. Exiting...\n"); - return -1; - } - else if(strlen((const char*)fname)==0) + //assert filename and not binary output + if(strlen((const char*)fname)==0) { fprintf(stderr, "Filename not specified. Exiting...\n"); return -1; @@ -218,12 +290,43 @@ int main(int argc, char* argv[]) //obtain data and append with CRC memset(full_packet_data, 0, 32*25); - if(fread(full_packet_data, num_bytes, 1, stdin)<1) + + //SMS Encode (-T) ./m17-packet-encode -f -o float.sym -T 'This is a simple SMS text message sent over M17 Packet Data.' + if (sms_encode == 1) { - fprintf(stderr, "Packet data too short. Exiting...\n"); - return -1; + num_bytes = strlen((const char*)text); //No need to check for zero return, since the default text string is supplied + if (num_bytes > 796) num_bytes = 796; //not 798 because we have to manually add the 0x05 protocol byte and 0x00 terminator + full_packet_data[0] = 0x05; //SMS Protocol + memcpy (full_packet_data+1, text, num_bytes); + num_bytes+= 2; //add one for terminating byte and 1 for strlen fix + fprintf (stderr, "SMS: %s\n", full_packet_data+1); } - + + //RAW Encode (-R) ./m17-packet-encode -f -o float.sym -R 5B69001E135152397C0A0000005A45 + else if (raw_encode == 1) + { + memcpy (full_packet_data, raw, num_bytes); + } + + //Old Method pre-encoded data over stdin // echo -en "\x05Testing M17 packet mode.\x00" | ./m17-packet-encode -S N0CALL -D AB1CDE -C 7 -n 26 -f -o float.sym + else if (std_encode == 1) + { + //assert number of bytes + if(num_bytes==0) + { + fprintf(stderr, "Number of bytes not set. Exiting...\n"); + return -1; + } + + if(fread(full_packet_data, num_bytes, 1, stdin)<1) + { + fprintf(stderr, "Packet data too short. Exiting...\n"); + return -1; + } + fprintf(stderr, "SMS: %s\n", full_packet_data+1); + // + } + uint16_t packet_crc=CRC_M17(full_packet_data, num_bytes); full_packet_data[num_bytes] =packet_crc>>8; full_packet_data[num_bytes+1]=packet_crc&0xFF; @@ -244,8 +347,7 @@ int main(int argc, char* argv[]) #else fprintf(stderr, "DST: %s\t%012lX\nSRC: %s\t%012lX\n", dst_raw, dst_encoded, src_raw, src_encoded); #endif - //fprintf(stderr, "DST: %02X %02X %02X %02X %02X %02X\n", lsf.dst[0], lsf.dst[1], lsf.dst[2], lsf.dst[3], lsf.dst[4], lsf.dst[5]); - //fprintf(stderr, "SRC: %02X %02X %02X %02X %02X %02X\n", lsf.src[0], lsf.src[1], lsf.src[2], lsf.src[3], lsf.src[4], lsf.src[5]); + fprintf(stderr, "CAN: %02d\n", can); fprintf(stderr, "Data CRC:\t%04hX\n", packet_crc); type=((uint16_t)0x01<<1)|((uint16_t)can<<7); //packet mode, content: data lsf.type[0]=(uint16_t)type>>8; @@ -296,7 +398,10 @@ int main(int argc, char* argv[]) //send packet frame syncword fill_syncword(full_packet, &pkt_sym_cnt, SYNC_PKT); - if(num_bytes>=25) + //the following examples produce exactly 25 bytes, which exactly one frame, but >= meant this would never produce a final frame with EOT bit set + //echo -en "\x05Testing M17 packet mo\x00" | ./m17-packet-encode -S N0CALL -D ALL -C 10 -n 23 -o float.sym -f + //./m17-packet-encode -S N0CALL -D ALL -C 10 -o float.sym -f -T 'this is a simple text' + if(num_bytes>25) //fix for frames that, with terminating byte and crc, land exactly on 25 bytes (or %25==0) { memcpy(pkt_chunk, &full_packet_data[pkt_cnt*25], 25); pkt_chunk[25]=pkt_cnt<<2; @@ -368,16 +473,16 @@ int main(int argc, char* argv[]) } num_bytes=tmp; //bring back the num_bytes value - - fprintf (stderr, "FULL: "); - for(uint8_t i=0; i