diff --git a/examples/encryption_keys.json b/examples/encryption_keys.json new file mode 100644 index 00000000..32ede659 --- /dev/null +++ b/examples/encryption_keys.json @@ -0,0 +1,19 @@ +{ + "keys": [ + { + "keyid": "0x0001", + "algid": "0x84", + "key": "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + }, + { + "keyid": "0x0002", + "algid": "0x81", + "key": "0123456789ABCDEF" + }, + { + "keyid": "0x1b50", + "algid": "0xaa", + "key": "0123456789" + } + ] +} diff --git a/lib/op25_repeater/lib/CMakeLists.txt b/lib/op25_repeater/lib/CMakeLists.txt index 36802999..b23214d4 100644 --- a/lib/op25_repeater/lib/CMakeLists.txt +++ b/lib/op25_repeater/lib/CMakeLists.txt @@ -66,6 +66,9 @@ list(APPEND op25_repeater_sources p25_framer.cc p25p1_fdma.cc p25_crypt_algs.cc + p25_crypt_adp.cc + p25_crypt_aes.cc + p25_crypt_des.cc p25p1_voice_encode.cc p25p1_voice_decode.cc p25p2_framer.cc diff --git a/lib/op25_repeater/lib/p25_crypt.h b/lib/op25_repeater/lib/p25_crypt.h new file mode 100644 index 00000000..37aa13a0 --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt.h @@ -0,0 +1,46 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef P25_CRYPT_H +#define P25_CRYPT_H + +#include +#include + +enum algid_type : uint8_t { + ALG_UNENCRYPTED = 0x80, + ALG_DES_OFB = 0x81, + ALG_AES_256 = 0x84, + ALG_ADP_RC4 = 0xAA +}; + +enum frame_type { FT_UNK = 0, FT_LDU1, FT_LDU2, FT_2V, FT_4V_0, FT_4V_1, FT_4V_2, FT_4V_3 }; +enum protocol_type { PT_UNK = 0, PT_P25_PHASE1, PT_P25_PHASE2 }; + +typedef std::vector packed_codeword; + +struct key_info { + key_info() : algid(0), key() {} + key_info(uint8_t a, const std::vector &k) : algid(a), key(k) {} + uint8_t algid; + std::vector key; +}; + +#endif /* P25_CRYPT_H */ diff --git a/lib/op25_repeater/lib/p25_crypt_adp.cc b/lib/op25_repeater/lib/p25_crypt_adp.cc new file mode 100644 index 00000000..47522be2 --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt_adp.cc @@ -0,0 +1,149 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "p25_crypt_adp.h" + +// constructor +p25_crypt_adp::p25_crypt_adp(log_ts& logger, int debug, int msgq_id) : + p25_crypt_alg(logger, debug, msgq_id) { + + fprintf(stderr, "%s p25_crypt_adp::p25_crypt_adp: loading ADP RC4 module\n", logts.get(d_msgq_id)); +} + +// destructor +p25_crypt_adp::~p25_crypt_adp() { + +} + +// prepare routine entry point +bool +p25_crypt_adp::prepare(uint16_t keyid, protocol_type pr_type, uint8_t *MI) { + d_pr_type = pr_type; + memcpy(d_mi, MI, sizeof(d_mi)); + + d_key_iter = d_keys.find(keyid); + if (d_key_iter == d_keys.end()) { + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_adp::prepare: keyid[0x%x] not found\n", logts.get(d_msgq_id), keyid); + } + return false; + } + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_adp::prepare: keyid[0x%x] found\n", logts.get(d_msgq_id), keyid); + } + d_position = 0; + d_pr_type = pr_type; + + // Find key value from keyid and set up to create keystream + uint8_t adp_key[13], S[256], K[256]; + uint32_t i, j, k; + std::vector::const_iterator kval_iter = d_key_iter->second.key.begin(); + for (i = 0; i < (uint32_t)std::max(5-(int)(d_key_iter->second.key.size()), 0); i++) { + adp_key[i] = 0; // pad with leading 0 if supplied key too short + } + for ( ; i < 5; i++) { + adp_key[i] = *kval_iter++; // copy up to 5 bytes into key array + } + + j = 0; + for (i = 5; i < 13; ++i) { + adp_key[i] = d_mi[i - 5]; // append MI bytes + } + + for (i = 0; i < 256; ++i) { + K[i] = adp_key[i % 13]; + } + + for (i = 0; i < 256; ++i) { + S[i] = i; + } + + for (i = 0; i < 256; ++i) { + j = (j + S[i] + K[i]) & 0xFF; + adp_swap(S, i, j); + } + + i = j = 0; + + for (k = 0; k < 469; ++k) { + i = (i + 1) & 0xFF; + j = (j + S[i]) & 0xFF; + adp_swap(S, i, j); + d_keystream[k] = S[(S[i] + S[j]) & 0xFF]; + } + + return true; +} + +// process routine entry point +bool +p25_crypt_adp::process(packed_codeword& PCW, frame_type fr_type, int voice_subframe) { + if (d_key_iter == d_keys.end()) + return false; + + bool rc = true; + size_t offset = 256; + switch (fr_type) { + case FT_LDU1: + offset = 0; + break; + case FT_LDU2: + offset = 101; + break; + case FT_4V_0: + offset += 7 * voice_subframe; + break; + case FT_4V_1: + offset += 7 * (voice_subframe + 4); + break; + case FT_4V_2: + offset += 7 * (voice_subframe + 8); + break; + case FT_4V_3: + offset += 7 * (voice_subframe + 12); + break; + case FT_2V: + offset += 7 * (voice_subframe + 16); + break; + default: + rc = false; + break; + } + if (d_pr_type == PT_P25_PHASE1) { + //FDMA + offset += (d_position * 11) + 267 + ((d_position < 8) ? 0 : 2); // voice only; skip LCW and LSD + d_position = (d_position + 1) % 9; + for (int j = 0; j < 11; ++j) { + PCW[j] = d_keystream[j + offset] ^ PCW[j]; + } + } else if (d_pr_type == PT_P25_PHASE2) { + //TDMA + for (int j = 0; j < 7; ++j) { + PCW[j] = d_keystream[j + offset] ^ PCW[j]; + } + PCW[6] &= 0x80; // mask everything except the MSB of the final codeword + } + + return rc; +} diff --git a/lib/op25_repeater/lib/p25_crypt_adp.h b/lib/op25_repeater/lib/p25_crypt_adp.h new file mode 100644 index 00000000..9eb92f56 --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt_adp.h @@ -0,0 +1,59 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef P25_CRYPT_ADP_H +#define P25_CRYPT_ADP_H + +#include +#include "log_ts.h" +#include "p25_crypt.h" +#include "p25_crypt_alg.h" + +// ADP RC4 decryption algorithm +class p25_crypt_adp : public p25_crypt_alg +{ + private: + protocol_type d_pr_type; + uint8_t d_mi[9]; + uint8_t d_keystream[469]; + uint32_t d_position; + + public: + virtual bool prepare(uint16_t keyid, protocol_type pr_type, uint8_t *MI); + virtual bool process(packed_codeword& PCW, frame_type fr_type, int voice_subframe); + + p25_crypt_adp(log_ts& logger, int debug, int msgq_id); + ~p25_crypt_adp(); + + inline uint8_t key(uint16_t keyid, uint8_t algid, const std::vector &key) { + if (algid != ALG_ADP_RC4) + return 0; + p25_crypt_alg::key(keyid, algid, key); + return algid; + } + + inline void adp_swap(uint8_t *S, uint32_t i, uint32_t j) { + uint8_t temp = S[i]; + S[i] = S[j]; + S[j] = temp; + } +}; + +#endif /* P25_CRYPT_ADP_H */ diff --git a/lib/op25_repeater/lib/p25_crypt_aes.cc b/lib/op25_repeater/lib/p25_crypt_aes.cc new file mode 100644 index 00000000..d93c3c98 --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt_aes.cc @@ -0,0 +1,1023 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * Adapted from aes.h/aes.c from github.com:/lwvmobile/tinier-aes + * which in turn was derived from https://github.com/kokke/tiny-AES-c + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "p25_crypt_aes.h" +#include "p25_crypt_algs.h" + +const uint8_t p25_crypt_aes::sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +const uint8_t p25_crypt_aes::rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + +const uint8_t p25_crypt_aes::Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +#define getSBoxValue(num) (p25_crypt_aes::sbox[(num)]) +#define getSBoxInvert(num) (p25_crypt_aes::rsbox[(num)]) +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * p25_crypt_aes::xtime(x)) ^ \ + ((y>>2 & 1) * p25_crypt_aes::xtime(p25_crypt_aes::xtime(x))) ^ \ + ((y>>3 & 1) * p25_crypt_aes::xtime(p25_crypt_aes::xtime(p25_crypt_aes::xtime(x)))) ^ \ + ((y>>4 & 1) * p25_crypt_aes::xtime(p25_crypt_aes::xtime(p25_crypt_aes::xtime(p25_crypt_aes::xtime(x)))))) \ + +void +p25_crypt_aes::KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0]=RoundKey[k + 0]; + tempa[1]=RoundKey[k + 1]; + tempa[2]=RoundKey[k + 2]; + tempa[3]=RoundKey[k + 3]; + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } + + if (Nk == 8) //only run if using AES256 (Nk == 8) + { + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } + } + + j = i * 4; k=(i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +//input bit array, return output as up to a 64-bit value +uint64_t +p25_crypt_aes::convert_bits_into_output(uint8_t * input, int len) +{ + int i; + uint64_t output = 0; + for(i = 0; i < len; i++) + { + output <<= 1; + output |= (uint64_t)(input[i] & 1); + } + return output; +} + +//take x amount of bits and pack into len amount of bytes (symmetrical) +void +p25_crypt_aes::pack_bit_array_into_byte_array (uint8_t * input, uint8_t * output, int len) +{ + int i; + for (i = 0; i < len; i++) + output[i] = (uint8_t)convert_bits_into_output(&input[i*8], 8); +} + +//take len amount of bytes and unpack back into a bit array +void +p25_crypt_aes::unpack_byte_array_into_bit_array (uint8_t * input, uint8_t * output, int len) +{ + int i = 0, k = 0; + for (i = 0; i < len; i++) + { + output[k++] = (input[i] >> 7) & 1; + output[k++] = (input[i] >> 6) & 1; + output[k++] = (input[i] >> 5) & 1; + output[k++] = (input[i] >> 4) & 1; + output[k++] = (input[i] >> 3) & 1; + output[k++] = (input[i] >> 2) & 1; + output[k++] = (input[i] >> 1) & 1; + output[k++] = (input[i] >> 0) & 1; + } +} + +void +p25_crypt_aes::AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) +{ + KeyExpansion(ctx->RoundKey, key); +} + +void +p25_crypt_aes::AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} + +void +p25_crypt_aes::AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} + +void +p25_crypt_aes::AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) +{ + uint8_t i,j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +void +p25_crypt_aes::SubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +void +p25_crypt_aes::ShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +uint8_t +p25_crypt_aes::xtime(uint8_t x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +void +p25_crypt_aes::MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +void +p25_crypt_aes::InvMixColumns(state_t* state) +{ + int i; + uint8_t a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + +void +p25_crypt_aes::InvSubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +void +p25_crypt_aes::InvShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to right + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} + +// Cipher is the main function that encrypts the PlainText, +// or produces a keystream, depending on application. +void +p25_crypt_aes::Cipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +void +p25_crypt_aes::InvCipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (round = (Nr - 1); ; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } +} + +void +p25_crypt_aes::AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher((state_t*)buf, ctx->RoundKey); +} + +void +p25_crypt_aes::AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher((state_t*)buf, ctx->RoundKey); +} + +void +p25_crypt_aes::XorWithIv(uint8_t* buf, const uint8_t* Iv) +{ + uint8_t i; + for (i = 0; i < AES_BLOCKLEN; ++i) + { + buf[i] ^= Iv[i]; + } +} + +void +p25_crypt_aes::AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t *Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void +p25_crypt_aes::AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void +p25_crypt_aes::AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + uint8_t buffer[AES_BLOCKLEN]; + + size_t i; + int bi; + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)buffer,ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + buf[i] = (buf[i] ^ buffer[bi]); + } +} + +//byte-wise output of AES OFB Keystream +//input iv is a 16-byte uint8_t array of initialization vector +//input key is up to 32-byte uint8_t array of key value +//input type is the type/key len of AES required (0-128, 1-192, 2-256) +//input nblocks is the number of rounds of 16-byte keystream output blocks requried +//output is a uint8_t bytewise array, each round filled with 16-bytes from aes keystream output +void +p25_crypt_aes::aes_ofb_keystream_output (uint8_t * iv, uint8_t * key, uint8_t * output, int type, int nblocks) +{ + int i; + uint8_t input_register[16]; //OFB Input Register + memset (input_register, 0, sizeof(input_register)); + + //Set values specific to type (128/192/256) + if (type == 0) //128 + { + Nb = 4; + Nk = 4; + Nr = 10; + } + else if (type == 1) //192 + { + Nb = 4; + Nk = 6; + Nr = 12; + } + else //if (type == 2) //256 + { + Nb = 4; + Nk = 8; + Nr = 14; + } + + struct AES_ctx ctx; + + //load first round of input_register with received IV (OFB First Input Register) + memcpy (input_register, iv, 16*sizeof(uint8_t) ); + + //initialize the key variable for the Cipher function + memset (ctx.RoundKey, 0, 240*sizeof(uint8_t)); + KeyExpansion(ctx.RoundKey, key); + + //execute the cipher function, and copy ciphered input_register to output for required number of rounds + for (i = 0; i < nblocks; i++) + { + Cipher((state_t*)input_register, ctx.RoundKey); //input_register is returned as output, and is put back in as object feedback + memcpy (output+(i*16), input_register, 16*sizeof(uint8_t) ); //copy ciphered input_register to output + } +} + +//byte-wise AES CFB (Cipher Feedback) Payload operating in 128-bit block mode +//input in is a uint8_t bytewise array,is the input to be ciphered (encrypted or decrypted) +//input iv is a 16-byte uint8_t array of initialization vector +//input key is up to 32-byte uint8_t array of key value +//input type is the type/key len of AES required (0-128, 1-192, 2-256) +//input nblocks is the number of rounds of 16-byte payload blocks requried (last block will need padding if not flush) +//output out is a uint8_t bytewise array, each round filled with 16-bytes of cfb ciphered output (encrypted or decrypted) +//de is a bit-flag signalling to run Cipher (encrypt) on 1, or InvCipher (decrypt) on 0 +void +p25_crypt_aes::aes_cfb_bytewise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * in, uint8_t * out, int type, int nblocks, int de) +{ + int i, j; + uint8_t input_register[16]; //Input Register + memset (input_register, 0, sizeof(input_register)); + + //Set values specific to type (128/192/256) + if (type == 0) //128 + { + Nb = 4; + Nk = 4; + Nr = 10; + } + else if (type == 1) //192 + { + Nb = 4; + Nk = 6; + Nr = 12; + } + else //if (type == 2) //256 + { + Nb = 4; + Nk = 8; + Nr = 14; + } + + struct AES_ctx ctx; + + //load first round of input_register with received IV (CFB First Input Register) + memcpy (input_register, iv, 16*sizeof(uint8_t) ); + + //initialize the key variable for the Cipher function + memset (ctx.RoundKey, 0, 240*sizeof(uint8_t)); + KeyExpansion(ctx.RoundKey, key); + + //execute the cipher function, and copy ciphered input_register to output for required number of rounds + for (i = 0; i < nblocks; i++) + { + //the cipher is always run in the foward, or encryption mode + Cipher((state_t*)input_register, ctx.RoundKey); + + //xor the current input 'in' to the current state of the input_register for cipher feedback + for (j = 0; j < 16; j++) + input_register[j] ^= in[j+(i*16)]; + + //copy ciphered/xor'd input_register to output 'out' + memcpy (out+(i*16), input_register, 16*sizeof(uint8_t) ); + + //if running in decryption mode, we feed in the next round of input + if (!de) + memcpy(input_register, in+(i*16), 16*sizeof(uint8_t)); + } +} + +//byte-wise AES CBC (Cipher Block Chaining) //Yeah, I know this already exists, but wanted a custom convenience wrapper version +//input in is a uint8_t bytewise array,is the input to be ciphered (encrypted or decrypted) +//input iv is a 16-byte uint8_t array of initialization vector +//input key is up to 32-byte uint8_t array of key value +//input type is the type/key len of AES required (0-128, 1-192, 2-256) +//input nblocks is the number of rounds of 16-byte payload blocks requried (last block will need padding if not flush) +//output out is a uint8_t bytewise array, each round filled with 16-bytes of cfb ciphered output (encrypted or decrypted) +//de is a bit-flag signalling to run Cipher (encrypt) on 1, or InvCipher (decrypt) on 0 +void +p25_crypt_aes::aes_cbc_bytewise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * in, uint8_t * out, int type, int nblocks, int de) +{ + int i, j; + uint8_t input_register[16]; //Input Register + memset (input_register, 0, sizeof(input_register)); + + //Set values specific to type (128/192/256) + if (type == 0) //128 + { + Nb = 4; + Nk = 4; + Nr = 10; + } + else if (type == 1) //192 + { + Nb = 4; + Nk = 6; + Nr = 12; + } + else //if (type == 2) //256 + { + Nb = 4; + Nk = 8; + Nr = 14; + } + + struct AES_ctx ctx; + + //load first round of input_register accordingly + if (de) + memcpy (input_register, iv, 16*sizeof(uint8_t) ); + else memcpy (input_register, in, 16*sizeof(uint8_t) ); + + //initialize the key variable for the Cipher function + memset (ctx.RoundKey, 0, 240*sizeof(uint8_t)); + KeyExpansion(ctx.RoundKey, key); + + // + for (i = 0; i < nblocks; i++) + { + //run encryption or decryption depending on de value + if (de) //encrypt + { + //xor the current input 'in' pt to the current state of the input_register for cbc feedback + for (j = 0; j < 16; j++) + input_register[j] ^= in[j+(i*16)]; + + Cipher((state_t*)input_register, ctx.RoundKey); + + //copy ciphered input_register to output 'out' + memcpy (out+(i*16), input_register, 16*sizeof(uint8_t) ); + + } + + else //decrypt + { + InvCipher((state_t*)input_register, ctx.RoundKey); + + //copy ciphered input_register to output 'out' + memcpy (out+(i*16), input_register, 16*sizeof(uint8_t) ); + + //xor the current output by IV, or by last received CT + if (i == 0) + { + for (j = 0; j < 16; j++) + out[j] ^= iv[j]; + } + else + { + for (j = 0; j < 16; j++) + out[j+(i*16)] ^= in[j+((i-1)*16)]; + } + + //copy in next segment for input_register (if not last) + if (i < nblocks) + memcpy(input_register, in+((i+1)*16), 16*sizeof(uint8_t) ); + } + } +} + +//byte-wise AES CBC_MAC (Cipher Block Chaining Message Authentication) //This is slightly different than above, no IV is present, +//but if iv is desireable, it will need to be pre-XOR'd with the first plaintext input block by the calling function +//input in is a uint8_t bytewise array, is the input to be ciphered. +//input key is up to 32-byte uint8_t array of key value +//input type is the type/key len of AES required (0-128, 1-192, 2-256) +//input nblocks is the number of rounds of 16-byte payload blocks requried (last block will need padding if not flush) +//output out is a uint8_t bytewise array, with only the final round output as the MAC octets +//NOTE: When doing a cbc_mac, you should only run it in the forward (encryption) mode to get the mac bytes +void +p25_crypt_aes::aes_cbc_mac_generator (uint8_t * key, uint8_t * in, uint8_t * out, int type, int nblocks) +{ + int i, j; + uint8_t input_register[16]; //Input Register + memset (input_register, 0, sizeof(input_register)); + + //Set values specific to type (128/192/256) + if (type == 0) //128 + { + Nb = 4; + Nk = 4; + Nr = 10; + } + else if (type == 1) //192 + { + Nb = 4; + Nk = 6; + Nr = 12; + } + else //if (type == 2) //256 + { + Nb = 4; + Nk = 8; + Nr = 14; + } + + struct AES_ctx ctx; + + //initialize the key variable for the Cipher function + memset (ctx.RoundKey, 0, 240*sizeof(uint8_t)); + KeyExpansion(ctx.RoundKey, key); + + // + for (i = 0; i < nblocks; i++) + { + //xor the current input 'in' pt to the current state of the input_register for cbc feedback + //if this is the first iteration, this will load the first round plain text instead + for (j = 0; j < 16; j++) + input_register[j] ^= in[j+((i+0)*16)]; + + Cipher((state_t*)input_register, ctx.RoundKey); + + //debug, load out all intermediate output register values + // memcpy (out+(i*16), input_register, 16*sizeof(uint8_t) ); + + } + + //copy final ciphered input_register to output 'out', user will determine how many bytes of output they want for MAC + memcpy (out, input_register, 16*sizeof(uint8_t) ); +} + +//byte-wise output of AES ECB Ciphering/Deciphering +//input is uint8_t byte-wise (16-bytes) data to be ciphered or deciphered +//input key is up to 32-byte uint8_t array of key value +//input type is the type/len of AES required (0-128, 1-192, 2-256) +//output is a uint8_t bytewise array of ciphered or deciphered input +//de is a bit-flag signalling to run Cipher (encrypt) on 1, or InvCipher (decrypt) on 0 +void +p25_crypt_aes::aes_ecb_bytewise_payload_crypt (uint8_t * input, uint8_t * key, uint8_t * output, int type, int de) +{ + uint8_t input_register[16]; //ECB Input Register + memset (input_register, 0, sizeof(input_register)); + + //Set values specific to type (128/192/256) + if (type == 0) //128 + { + Nb = 4; + Nk = 4; + Nr = 10; + } + else if (type == 1) //192 + { + Nb = 4; + Nk = 6; + Nr = 12; + } + else //if (type == 2) //256 + { + Nb = 4; + Nk = 8; + Nr = 14; + } + + struct AES_ctx ctx; + + //load input_register with received input (ECB Payload) + memcpy (input_register, input, 16*sizeof(uint8_t) ); + + //initialize the key variable for the Cipher function + memset (ctx.RoundKey, 0, 240*sizeof(uint8_t)); + KeyExpansion(ctx.RoundKey, key); + + //run encryption or decryption depending on de value + if (de) //encrypt + Cipher((state_t*)input_register, ctx.RoundKey); + else //decrypt + InvCipher((state_t*)input_register, ctx.RoundKey); + + //copy ciphered/deciphered input_register to output + memcpy (output, input_register, 16*sizeof(uint8_t) ); +} + +//symmetrical ctr mode payload encryption and decryption +void +p25_crypt_aes::aes_ctr_bitwise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * payload, int type) +{ + //Set values specific to type (128/192/256) + if (type == 0) //128 + { + Nb = 4; + Nk = 4; + Nr = 10; + } + else if (type == 1) //192 + { + Nb = 4; + Nk = 6; + Nr = 12; + } + else //if (type == 2) //256 + { + Nb = 4; + Nk = 8; + Nr = 14; + } + + struct AES_ctx ctx; + + //init and set the iv and key variables + memset (ctx.RoundKey, 0, 240*sizeof(uint8_t)); + memset (ctx.Iv, 0, 16*sizeof(uint8_t)); + + KeyExpansion(ctx.RoundKey, key); + memcpy (ctx.Iv, iv, AES_BLOCKLEN); + + //pack input bit-wise payload to byte array + uint8_t payload_bytes[16]; + memset (payload_bytes, 0, sizeof(payload_bytes)); + pack_bit_array_into_byte_array (payload, payload_bytes, 16); + + //pass to internal CTR handler for payload + AES_CTR_xcrypt_buffer(&ctx, payload_bytes, 16); + + //unpack output bytes back to bits + unpack_byte_array_into_bit_array(payload_bytes, payload, 16); +} + +//symmetrical ctr mode payload encryption and decryption +void +p25_crypt_aes::aes_ctr_bytewise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * payload, int type) +{ + //Set values specific to type (128/192/256) + if (type == 0) //128 + { + Nb = 4; + Nk = 4; + Nr = 10; + } + else if (type == 1) //192 + { + Nb = 4; + Nk = 6; + Nr = 12; + } + else //if (type == 2) //256 + { + Nb = 4; + Nk = 8; + Nr = 14; + } + + struct AES_ctx ctx; + + //init and set the iv and key variables + memset (ctx.RoundKey, 0, 240*sizeof(uint8_t)); + memset (ctx.Iv, 0, 16*sizeof(uint8_t)); + + KeyExpansion(ctx.RoundKey, key); + memcpy (ctx.Iv, iv, AES_BLOCKLEN); + + //pass to internal CTR handler for payload + AES_CTR_xcrypt_buffer(&ctx, payload, 16); +} + +// constructor +p25_crypt_aes::p25_crypt_aes(log_ts& logger, int debug, int msgq_id) : + p25_crypt_alg(logger, debug, msgq_id) { + + fprintf(stderr, "%s p25_crypt_aes::p25_crypt_aes: loading AES 256 (OFB) module\n", logts.get(d_msgq_id)); + } + +// destructor +p25_crypt_aes::~p25_crypt_aes() { + +} + +// prepare routine entry point +bool +p25_crypt_aes::prepare(uint16_t keyid, protocol_type pr_type, uint8_t *MI) { + d_pr_type = pr_type; + d_key_iter = d_keys.find(keyid); + if (d_key_iter == d_keys.end()) { + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_aes::prepare: keyid[0x%x] not found\n", logts.get(d_msgq_id), keyid); + } + return false; + } + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_aes::prepare: keyid[0x%x] found\n", logts.get(d_msgq_id), keyid); + } + + // Expand MI to create proper IV + uint8_t IV[16]; + p25_crypt_algs::expand_mi_to_128(MI, IV); + + // Find key value from keyid and set up to create keystream + uint8_t Key[32]; + uint32_t i; + std::vector::const_iterator kval_iter = d_key_iter->second.key.begin(); + for (i = 0; i < (uint32_t)std::max(32 - (int)(d_key_iter->second.key.size()), 0); i++) { + Key[i] = 0; // pad with leading 0 if supplied key is too short + } + for (; i < 32; i++) { + Key[i] = *kval_iter++; // copy up to 32 bytes into key array + } + + // Run the crypt routine to create a keystream long enough for the whole superframe + // Length is dependent on protocol: FDMA=15 blocks, TDMA=9 blocks. + aes_ofb_keystream_output (IV, Key, d_keystream, 2, ((pr_type == PT_P25_PHASE2) ? 9 : 15)); + d_position = 0; + + return true; +} + +// process routine entry point +bool +p25_crypt_aes::process(packed_codeword& PCW, frame_type fr_type, int voice_subframe) { + if (d_key_iter == d_keys.end()) + return false; + + bool rc = true; + size_t offset = 16; //initial offset is 16 (AES-OFB discard round) + + switch (fr_type) { + /* FDMA */ + case FT_LDU1: + offset += 0; //additional offset for FDMA is handled below + break; + case FT_LDU2: + offset += 101; //additional offset for FDMA is handled below + break; + /* TDMA */ + case FT_4V_0: + offset += 7 * voice_subframe; + break; + case FT_4V_1: + offset += 7 * (voice_subframe + 4); + break; + case FT_4V_2: + offset += 7 * (voice_subframe + 8); + break; + case FT_4V_3: + offset += 7 * (voice_subframe + 12); + break; + case FT_2V: + offset += 7 * (voice_subframe + 16); + break; + default: + rc = false; + break; + } + + if (d_pr_type == PT_P25_PHASE1) { + //FDMA + offset += (d_position * 11) + 11 + ((d_position < 8) ? 0 : 2); // voice only; skip 9 LCW bytes, 2 reserved bytes, and LSD between 7,8 and 16,17 + d_position = (d_position + 1) % 9; + for (int j = 0; j < 11; ++j) { + PCW[j] = d_keystream[j + offset] ^ PCW[j]; + } + +#if 0 + //debug, print keystream values and track offset + if (d_debug >= 10) { + fprintf (stderr, "%s AES KS: ", logts.get(d_msgq_id)); + for (int j = 0; j < 7; ++j) { + fprintf (stderr, "%02X", d_keystream[j + offset]); + } + fprintf (stderr, " Offset: %ld; \n", offset); + } +#endif + + } else if (d_pr_type == PT_P25_PHASE2) { + //TDMA - Experimental + for (int j = 0; j < 7; ++j) { + PCW[j] = d_keystream[j + offset] ^ PCW[j]; + } + PCW[6] &= 0x80; // mask everything except the MSB of the final codeword + +#if 0 + //debug, print keystream values and track offset + if (d_debug >= 10) { + fprintf (stderr, "%s AES KS: ", logts.get(d_msgq_id)); + for (int j = 0; j < 7; ++j) { + fprintf (stderr, "%02X", d_keystream[j + offset]); + } + fprintf (stderr, " Offset: %ld; \n", offset); + } +#endif + + } + return rc; +} diff --git a/lib/op25_repeater/lib/p25_crypt_aes.h b/lib/op25_repeater/lib/p25_crypt_aes.h new file mode 100644 index 00000000..179d7d2e --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt_aes.h @@ -0,0 +1,113 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * Adapted from aes.h/aes.c from github.com:/lwvmobile/tinier-aes + * which in turn was derived from https://github.com/kokke/tiny-AES-c + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef P25_CRYPT_AES_H +#define P25_CRYPT_AES_H + +#include +#include +#include +#include "log_ts.h" +#include "p25_crypt.h" +#include "p25_crypt_alg.h" + +#define AES_BLOCKLEN 16 + +// DES OFB decryption algorithm +class p25_crypt_aes : public p25_crypt_alg +{ + private: + protocol_type d_pr_type; + uint8_t d_keystream[240]; // 16 bytes per block x 15 blocks (FDMA), or 9 blocks (TDMA) + uint32_t d_position; + + unsigned Nb = 4; + unsigned Nk = 8; + unsigned Nr = 14; + + public: + virtual bool prepare(uint16_t keyid, protocol_type pr_type, uint8_t *MI); + virtual bool process(packed_codeword& PCW, frame_type fr_type, int voice_subframe); + + p25_crypt_aes(log_ts& logger, int debug, int msgq_id); + ~p25_crypt_aes(); + + inline uint8_t key(uint16_t keyid, uint8_t algid, const std::vector &key) { + if (algid != ALG_AES_256) + return 0; + p25_crypt_alg::key(keyid, algid, key); + return algid; + } + + private: + // everything that follows is derived from tinier-aes + struct AES_ctx + { + uint8_t RoundKey[240]; + uint8_t Iv[16]; + }; + + typedef uint8_t state_t[4][4]; + + static const uint8_t sbox[256]; + static const uint8_t rsbox[256]; + static const uint8_t Rcon[11]; + + // bit and byte utility prototyes + uint64_t convert_bits_into_output(uint8_t * input, int len); + void pack_bit_array_into_byte_array (uint8_t * input, uint8_t * output, int len); + void unpack_byte_array_into_bit_array (uint8_t * input, uint8_t * output, int len); + uint8_t xtime(uint8_t x); + void XorWithIv(uint8_t* buf, const uint8_t* Iv); + + // aes function prototypes, convenience wrapper functions + void aes_ctr_bitwise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * payload, int type); + void aes_ctr_bytewise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * payload, int type); + void aes_ofb_keystream_output (uint8_t * iv, uint8_t * key, uint8_t * output, int type, int nblocks); + void aes_cfb_bytewise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * in, uint8_t * out, int type, int nblocks, int de); + void aes_cbc_bytewise_payload_crypt (uint8_t * iv, uint8_t * key, uint8_t * in, uint8_t * out, int type, int nblocks, int de); + void aes_cbc_mac_generator (uint8_t * key, uint8_t * in, uint8_t * out, int type, int nblocks); + void aes_ecb_bytewise_payload_crypt (uint8_t * input, uint8_t * key, uint8_t * output, int type, int de); + + // internal function prototypes + void Cipher(state_t* state, const uint8_t* RoundKey); + void InvCipher(state_t* state, const uint8_t* RoundKey); + void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key); + void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey); + void SubBytes(state_t* state); + void ShiftRows(state_t* state); + void MixColumns(state_t* state); + void InvMixColumns(state_t* state); + void InvSubBytes(state_t* state); + void InvShiftRows(state_t* state); + void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); + void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); + void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); + void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); + void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); + void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +}; + +#endif /* P25_CRYPT_AES_H */ diff --git a/lib/op25_repeater/lib/p25_crypt_alg.h b/lib/op25_repeater/lib/p25_crypt_alg.h new file mode 100644 index 00000000..e1f85d9d --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt_alg.h @@ -0,0 +1,58 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef P25_CRYPT_ALG_H +#define P25_CRYPT_ALG_H + +#include +#include + +#include "p25_crypt.h" +#include "log_ts.h" + +// Base class for implementation of individual decryption algorithms +class p25_crypt_alg +{ + protected: + log_ts& logts; + int d_debug; + int d_msgq_id; + std::unordered_map d_keys; + std::unordered_map::const_iterator d_key_iter; + + public: + virtual bool prepare(uint16_t keyid, protocol_type pr_type, uint8_t *MI) = 0; + virtual bool process(packed_codeword& PCW, frame_type fr_type, int voice_subframe) = 0; + + inline p25_crypt_alg(log_ts& logger, int debug, int msgq_id) : logts(logger), d_debug(debug), d_msgq_id(msgq_id) { } + inline virtual ~p25_crypt_alg() { } + + inline virtual void reset(void) { d_keys.clear(); } + inline virtual void set_debug(int debug) { d_debug = debug; } + inline virtual uint8_t key(uint16_t keyid, uint8_t algid, const std::vector &key) { + if ((keyid == 0) || (algid == ALG_UNENCRYPTED)) + return 0; + d_keys[keyid] = key_info(algid, key); + return algid; + } + +}; + +#endif /* P25_CRYPT_ALG_H */ diff --git a/lib/op25_repeater/lib/p25_crypt_algs.cc b/lib/op25_repeater/lib/p25_crypt_algs.cc index d3e6f19e..4152dc45 100644 --- a/lib/op25_repeater/lib/p25_crypt_algs.cc +++ b/lib/op25_repeater/lib/p25_crypt_algs.cc @@ -1,17 +1,17 @@ /* -*- c++ -*- */ -/* - * Copyright 2022 Graham J. Norbury - * +/* + * Copyright 2025 Graham J. Norbury + * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. - * + * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, @@ -25,7 +25,12 @@ #include #include +#include "p25_crypt.h" #include "p25_crypt_algs.h" +#include "p25_crypt_adp.h" +#include "p25_crypt_des.h" +#include "p25_crypt_aes.h" + #include "op25_msg_types.h" // constructor @@ -33,21 +38,24 @@ p25_crypt_algs::p25_crypt_algs(log_ts& logger, int debug, int msgq_id) : logts(logger), d_debug(debug), d_msgq_id(msgq_id), - d_pr_type(PT_UNK), - d_algid(0x80), - d_keyid(0), - d_mi{0}, - d_key_iter(d_keys.end()), - d_adp_position(0) { + d_alg_iter(d_algs.end()) { } // destructor p25_crypt_algs::~p25_crypt_algs() { + // clean up dynamically allocated decryption algorithm objects + d_alg_iter = d_algs.begin(); + while (d_alg_iter != d_algs.end()) { + delete d_alg_iter->second; + d_algs.erase(d_alg_iter); + } } // remove all stored keys void p25_crypt_algs::reset(void) { - d_keys.clear(); + for (auto& it : d_algs) { + it.second->reset(); + } } // add or update a key @@ -55,161 +63,107 @@ void p25_crypt_algs::key(uint16_t keyid, uint8_t algid, const std::vectorsecond->key(keyid, algid, key) == algid) { + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_algs::key: loaded key keyId:%04x, algId:%02x\n", logts.get(d_msgq_id), keyid, algid); + } + } } // generic entry point to prepare for decryption bool p25_crypt_algs::prepare(uint8_t algid, uint16_t keyid, protocol_type pr_type, uint8_t *MI) { - bool rc = false; - d_algid = algid; - d_keyid = keyid; - memcpy(d_mi, MI, sizeof(d_mi)); - - d_key_iter = d_keys.find(keyid); - if (d_key_iter == d_keys.end()) { + d_alg_iter = d_algs.find(algid); + if (d_alg_iter == d_algs.end()) { if (d_debug >= 10) { - fprintf(stderr, "%s p25_crypt_algs::prepare: keyid[0x%x] not found\n", logts.get(d_msgq_id), keyid); + fprintf(stderr, "%s p25_crypt_algs::prepare: algid[0x%x] algorithm module not found\n", logts.get(d_msgq_id), algid); } - return rc; - } - if (d_debug >= 10) { - fprintf(stderr, "%s p25_crypt_algs::prepare: keyid[0x%x] found\n", logts.get(d_msgq_id), keyid); - } - - switch (algid) { - case 0xaa: // ADP RC4 - d_adp_position = 0; - d_pr_type = pr_type; - adp_keystream_gen(); - rc = true; - break; - - default: - break; + return false; } - return rc; + return d_alg_iter->second->prepare(keyid, pr_type, MI); } // generic entry point to perform decryption bool p25_crypt_algs::process(packed_codeword& PCW, frame_type fr_type, int voice_subframe) { - bool rc = false; - - if (d_key_iter == d_keys.end()) - return false; - - switch (d_algid) { - case 0xaa: // ADP RC4 - rc = adp_process(PCW, fr_type, voice_subframe); - break; - - default: - break; - } - - return rc; -} - -// ADP RC4 decryption -bool p25_crypt_algs::adp_process(packed_codeword& PCW, frame_type fr_type, int voice_subframe) { - bool rc = true; - size_t offset = 256; - - if (d_key_iter == d_keys.end()) - return false; - - switch (fr_type) { - case FT_LDU1: - offset = 0; - break; - case FT_LDU2: - offset = 101; - break; - case FT_4V_0: - offset += 7 * voice_subframe; - break; - case FT_4V_1: - offset += 7 * (voice_subframe + 4); - break; - case FT_4V_2: - offset += 7 * (voice_subframe + 8); - break; - case FT_4V_3: - offset += 7 * (voice_subframe + 12); - break; - case FT_2V: - offset += 7 * (voice_subframe + 16); - break; - default: - rc = false; - break; - } - if (d_pr_type == PT_P25_PHASE1) { - //FDMA - offset += (d_adp_position * 11) + 267 + ((d_adp_position < 8) ? 0 : 2); // voice only; skip LCW and LSD - d_adp_position = (d_adp_position + 1) % 9; - for (int j = 0; j < 11; ++j) { - PCW[j] = adp_keystream[j + offset] ^ PCW[j]; - } - } else if (d_pr_type == PT_P25_PHASE2) { - //TDMA - for (int j = 0; j < 7; ++j) { - PCW[j] = adp_keystream[j + offset] ^ PCW[j]; + if (d_alg_iter == d_algs.end()) { + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_algs::process: internal error (no algorithm module)\n", logts.get(d_msgq_id)); } - PCW[6] &= 0x80; // mask everything except the MSB of the final codeword + return false; } - - return rc; + return d_alg_iter->second->process(PCW, fr_type, voice_subframe); } -// ADP RC4 helper routine to swap two bytes -void p25_crypt_algs::adp_swap(uint8_t *S, uint32_t i, uint32_t j) { - uint8_t temp = S[i]; - S[i] = S[j]; - S[j] = temp; +// P25 variant of LFSR routine +// Returns MSB before shift takes place +uint64_t p25_crypt_algs::step_p25_lfsr(uint64_t &lfsr) { + // Polynomial is C(x) = x^64 + x^62 + x^46 + x^38 + x^27 + x^15 + 1 + uint64_t ov_bit = (lfsr >> 63) & 0x1; + uint64_t fb_bit = ((lfsr >> 63) ^ (lfsr >> 61) ^ (lfsr >> 45) ^ (lfsr >> 37) ^ (lfsr >> 26) ^ (lfsr >> 14)) & 0x1; + lfsr = (lfsr << 1) | (fb_bit); + return ov_bit; } -// ADP RC4 create keystream using supplied key and message_indicator -void p25_crypt_algs::adp_keystream_gen() { - uint8_t adp_key[13], S[256], K[256]; - uint32_t i, j, k; - - if (d_key_iter == d_keys.end()) - return; - - // Find key value from keyid and set up to create keystream - std::vector::const_iterator kval_iter = d_key_iter->second.key.begin(); - for (i = 0; i < (uint32_t)std::max(5-(int)(d_key_iter->second.key.size()), 0); i++) { - adp_key[i] = 0; // pad with leading 0 if supplied key too short - } - for ( ; i < 5; i++) { - adp_key[i] = *kval_iter++; // copy up to 5 bytes into key array +// Use P25 LFSR to replace current MI with the next one in the sequence +void p25_crypt_algs::cycle_p25_mi(uint8_t *MI) { + uint64_t lfsr = 0; + for (int i=0; i<8; i++) { + lfsr = (lfsr << 8) + MI[i]; } - j = 0; - for (i = 5; i < 13; ++i) { - adp_key[i] = d_mi[i - 5]; // append MI bytes + for(uint8_t cnt=0; cnt<64; cnt++) { + step_p25_lfsr(lfsr); } - for (i = 0; i < 256; ++i) { - K[i] = adp_key[i % 13]; + for (int i=7; i>=0; i--) { + MI[i] = lfsr & 0xFF; + lfsr >>= 8; } + MI[8] = 0; +} - for (i = 0; i < 256; ++i) { - S[i] = i; +// Expand MI to 128 bits for use as an Initialization Vector +void p25_crypt_algs::expand_mi_to_128(uint8_t *MI, uint8_t *IV) { + // copy first 64 bits of MI into LFSR + uint64_t lfsr = 0; + for (int i=0; i<8; i++) { + lfsr = (lfsr << 8) + MI[i]; } - for (i = 0; i < 256; ++i) { - j = (j + S[i] + K[i]) & 0xFF; - adp_swap(S, i, j); + // use LFSR routine to compute the expansion + uint64_t overflow = 0; + for (int i = 0; i < 64; i++) { + overflow = (overflow << 1) | step_p25_lfsr(lfsr); } - i = j = 0; - - for (k = 0; k < 469; ++k) { - i = (i + 1) & 0xFF; - j = (j + S[i]) & 0xFF; - adp_swap(S, i, j); - adp_keystream[k] = S[(S[i] + S[j]) & 0xFF]; + // copy expansion and lfsr to IV + for (int i=7; i>=0; i--) { + IV[i] = overflow & 0xFF; + overflow >>= 8; + } + for (int i=15; i>=8; i--) { + IV[i] = lfsr & 0xFF; + lfsr >>= 8; } } - diff --git a/lib/op25_repeater/lib/p25_crypt_algs.h b/lib/op25_repeater/lib/p25_crypt_algs.h index 270f36a5..9a48de9a 100644 --- a/lib/op25_repeater/lib/p25_crypt_algs.h +++ b/lib/op25_repeater/lib/p25_crypt_algs.h @@ -1,62 +1,41 @@ /* -*- c++ -*- */ -/* - * Copyright 2022 Graham J. Norbury - * +/* + * Copyright 2025 Graham J. Norbury + * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. - * + * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ -#ifndef INCLUDED_OP25_REPEATER_P25_CRYPT_ALGS_H -#define INCLUDED_OP25_REPEATER_P25_CRYPT_ALGS_H +#ifndef P25_CRYPT_ALGS_H +#define P25_CRYPT_ALGS_H #include -#include #include +#include "p25_crypt.h" +#include "p25_crypt_alg.h" #include "log_ts.h" -typedef std::vector packed_codeword; - -struct key_info { - key_info() : algid(0), key() {} - key_info(uint8_t a, const std::vector &k) : algid(a), key(k) {} - uint8_t algid; - std::vector key; -}; - -enum frame_type { FT_UNK = 0, FT_LDU1, FT_LDU2, FT_2V, FT_4V_0, FT_4V_1, FT_4V_2, FT_4V_3 }; -enum protocol_type { PT_UNK = 0, PT_P25_PHASE1, PT_P25_PHASE2 }; - class p25_crypt_algs { private: log_ts& logts; int d_debug; int d_msgq_id; - protocol_type d_pr_type; - uint8_t d_algid; - uint16_t d_keyid; - uint8_t d_mi[9]; - std::unordered_map d_keys; - std::unordered_map::const_iterator d_key_iter; - uint8_t adp_keystream[469]; - uint32_t d_adp_position; - - bool adp_process(packed_codeword& PCW, frame_type fr_type, int voice_subframe); - void adp_keystream_gen(); - void adp_swap(uint8_t *S, uint32_t i, uint32_t j); + std::unordered_map d_algs; + std::unordered_map::const_iterator d_alg_iter; public: p25_crypt_algs(log_ts& logger, int debug, int msgq_id); @@ -67,6 +46,10 @@ class p25_crypt_algs bool process(packed_codeword& PCW, frame_type fr_type, int voice_subframe); void reset(void); inline void set_debug(int debug) {d_debug = debug;} + + static void cycle_p25_mi(uint8_t *MI); + static void expand_mi_to_128(uint8_t *MI, uint8_t *IV); + static uint64_t step_p25_lfsr(uint64_t &lfsr); }; -#endif /* INCLUDED_OP25_REPEATER_P25_CRYPT_ALGS_H */ +#endif /* P25_CRYPT_ALGS_H */ diff --git a/lib/op25_repeater/lib/p25_crypt_des.cc b/lib/op25_repeater/lib/p25_crypt_des.cc new file mode 100644 index 00000000..f5ea0314 --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt_des.cc @@ -0,0 +1,483 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * Developed from code originally written by Ali Elmasry + * and adapted for op25 by Joey Absi + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "p25_crypt_des.h" + +// constructor +p25_crypt_des::p25_crypt_des(log_ts& logger, int debug, int msgq_id) : + p25_crypt_alg(logger, debug, msgq_id) { + + fprintf(stderr, "%s p25_crypt_des::p25_crypt_des: loading DES OFB module\n", logts.get(d_msgq_id)); +} + +// destructor +p25_crypt_des::~p25_crypt_des() { + +} + +// prepare routine entry point +bool +p25_crypt_des::prepare(uint16_t keyid, protocol_type pr_type, uint8_t *MI) { + d_key_iter = d_keys.find(keyid); + if (d_key_iter == d_keys.end()) { + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_des::prepare: keyid[0x%x] not found\n", logts.get(d_msgq_id), keyid); + } + return false; + } + if (d_debug >= 10) { + fprintf(stderr, "%s p25_crypt_des::prepare: keyid[0x%x] found\n", logts.get(d_msgq_id), keyid); + } + d_pr_type = pr_type; + d_position = 0; + + std::string key, mi, ct; + uint8_t des_key[8]; + uint32_t i; + + // Find key value from keyid and set up to create keystream + std::vector::const_iterator kval_iter = d_key_iter->second.key.begin(); + for (i = 0; i < (uint32_t)std::max(8 - (int)(d_key_iter->second.key.size()), 0); i++) { + des_key[i] = 0; // pad with leading 0 if supplied key too short + } + for (; i < 8; i++) { + des_key[i] = *kval_iter++; // copy up to 8 bytes into key array + } + + // Append supplied key array to string + key = byteArray2string(des_key); + + // Append supplied MI to string + mi = byteArray2string(MI); + + // Key Generation, Hex to Binary + key = hex2bin(key); + + // Parity bit drop table + int keyp[56] = { 57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4 }; + + // getting 56 bit key from 64 bit using the parity bits + key = permute(key, keyp, 56); // key without parity + + // Number of bit shifts + int shift_table[16] = { 1, 1, 2, 2, + 2, 2, 2, 2, + 1, 2, 2, 2, + 2, 2, 2, 1 }; + + // Key-Compression Table + int key_comp[48] = { 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 }; + + // Splitting + std::string left = key.substr(0, 28); + std::string right = key.substr(28, 28); + + std::vector rkb; // rkb for RoundKeys in binary + std::vector rk; // rk for RoundKeys in hexadecimal + for (int i = 0; i < 16; i++) { + // Shifting + left = shift_left(left, shift_table[i]); + right = shift_left(right, shift_table[i]); + + // Combining + std::string combine = left + right; + + // Key Compression + std::string RoundKey = permute(combine, key_comp, 48); + + rkb.push_back(RoundKey); + rk.push_back(bin2hex(RoundKey)); + } + + // OFB mode + int offset = 0; + for (int i = 0; i < 28; i++) { + if (i == 0) { + // First run using supplied IV + ct = encrypt(mi, rkb, rk); + } else { + ct = encrypt(ct, rkb, rk); + } + + // Append keystream to ks_array + string2ByteArray(ct, d_keystream, offset); + + // Increment offset by 8 for next round + offset += 8; + } + + return true; +} + +// process routine entry point +bool +p25_crypt_des::process(packed_codeword& PCW, frame_type fr_type, int voice_subframe) { + if (d_key_iter == d_keys.end()) + return false; + + bool rc = true; + size_t offset = 8; //initial offset is 8 (DES-OFB discard round) + + switch (fr_type) { + /* FDMA */ + case FT_LDU1: + offset += 0; //additional offset for FDMA is handled below + break; + case FT_LDU2: + offset += 101; //additional offset for FDMA is handled below + break; + /* TDMA */ + case FT_4V_0: + offset += 7 * voice_subframe; + break; + case FT_4V_1: + offset += 7 * (voice_subframe + 4); + break; + case FT_4V_2: + offset += 7 * (voice_subframe + 8); + break; + case FT_4V_3: + offset += 7 * (voice_subframe + 12); + break; + case FT_2V: + offset += 7 * (voice_subframe + 16); + break; + default: + rc = false; + break; + } + + if (d_pr_type == PT_P25_PHASE1) { + //FDMA + offset += (d_position * 11) + 11 + ((d_position < 8) ? 0 : 2); // voice only; skip 9 LCW bytes, 2 reserved bytes, and LSD between 7,8 and 16,17 + d_position = (d_position + 1) % 9; + for (int j = 0; j < 11; ++j) { + PCW[j] = d_keystream[j + offset] ^ PCW[j]; + } + +#if 0 + //debug, print keystream values and track offset + if (d_debug >= 10) { + fprintf (stderr, "%s DES KS: ", logts.get(d_msgq_id)); + for (int j = 0; j < 7; ++j) { + fprintf (stderr, "%02X", d_keystream[j + offset]); + } + fprintf (stderr, " Offset: %ld; \n", offset); + } +#endif + + } else if (d_pr_type == PT_P25_PHASE2) { + //TDMA - Experimental + for (int j = 0; j < 7; ++j) { + PCW[j] = d_keystream[j + offset] ^ PCW[j]; + } + PCW[6] &= 0x80; // mask everything except the MSB of the final codeword + +#if 0 + //debug, print keystream values and track offset + if (d_debug >= 10) { + fprintf (stderr, "%s DES KS: ", logts.get(d_msgq_id)); + for (int j = 0; j < 7; ++j) { + fprintf (stderr, "%02X", d_keystream[j + offset]); + } + fprintf (stderr, " Offset: %ld; \n", offset); + } +#endif + + } + return rc; +} + +std::string +p25_crypt_des::hex2bin(std::string s) { + // hexadecimal to binary conversion + std::unordered_map mp; + mp['0'] = "0000"; + mp['1'] = "0001"; + mp['2'] = "0010"; + mp['3'] = "0011"; + mp['4'] = "0100"; + mp['5'] = "0101"; + mp['6'] = "0110"; + mp['7'] = "0111"; + mp['8'] = "1000"; + mp['9'] = "1001"; + mp['A'] = "1010"; + mp['B'] = "1011"; + mp['C'] = "1100"; + mp['D'] = "1101"; + mp['E'] = "1110"; + mp['F'] = "1111"; + std::string bin = ""; + for (int i = 0; i < (int)s.size(); i++) { + bin += mp[s[i]]; + } + return bin; +} + +std::string +p25_crypt_des::bin2hex(std::string s) { + // binary to hexadecimal conversion + std::unordered_map mp; + mp["0000"] = "0"; + mp["0001"] = "1"; + mp["0010"] = "2"; + mp["0011"] = "3"; + mp["0100"] = "4"; + mp["0101"] = "5"; + mp["0110"] = "6"; + mp["0111"] = "7"; + mp["1000"] = "8"; + mp["1001"] = "9"; + mp["1010"] = "A"; + mp["1011"] = "B"; + mp["1100"] = "C"; + mp["1101"] = "D"; + mp["1110"] = "E"; + mp["1111"] = "F"; + std::string hex = ""; + for (int i = 0; i < (int)s.length(); i += 4) { + std::string ch = ""; + ch += s[i]; + ch += s[i + 1]; + ch += s[i + 2]; + ch += s[i + 3]; + hex += mp[ch]; + } + return hex; +} + +std::string +p25_crypt_des::permute(std::string k, int* arr, int n) { + std::string per = ""; + for (int i = 0; i < n; i++) { + per += k[arr[i] - 1]; + } + return per; +} + +std::string +p25_crypt_des::shift_left(std::string k, int shifts) { + std::string s = ""; + for (int i = 0; i < shifts; i++) { + for (int j = 1; j < 28; j++) { + s += k[j]; + } + s += k[0]; + k = s; + s = ""; + } + return k; +} + +std::string +p25_crypt_des::xor_(std::string a, std::string b) { + std::string ans = ""; + for (int i = 0; i < (int)a.size(); i++) { + if (a[i] == b[i]) { + ans += "0"; + } + else { + ans += "1"; + } + } + return ans; +} + +std::string +p25_crypt_des::encrypt(std::string pt, std::vector rkb, std::vector rk) { + // Hexadecimal to binary + pt = hex2bin(pt); + + // Initial Permutation Table + int initial_perm[64] = { 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7 }; + // Initial Permutation + pt = permute(pt, initial_perm, 64); + //cout << "After initial permutation: " << bin2hex(pt) << endl; + + // Splitting + std::string left = pt.substr(0, 32); + std::string right = pt.substr(32, 32); + //cout << "After splitting: L0=" << bin2hex(left) << " R0=" << bin2hex(right) << endl; + + // Expansion D-box Table + int exp_d[48] = { 32, 1, 2, 3, 4, 5, 4, 5, + 6, 7, 8, 9, 8, 9, 10, 11, + 12, 13, 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, 20, 21, + 22, 23, 24, 25, 24, 25, 26, 27, + 28, 29, 28, 29, 30, 31, 32, 1 }; + + // S-box Table + int s[8][4][16] = { { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, + 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, + 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, + 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 }, + { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, + 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, + 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, + 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 }, + + { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, + 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, + 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, + 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 }, + { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, + 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, + 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, + 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 }, + { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, + 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, + 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, + 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 }, + { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, + 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, + 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, + 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 }, + { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, + 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, + 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, + 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 }, + { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, + 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, + 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, + 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } }; + + // Straight Permutation Table + int per[32] = { 16, 7, 20, 21, + 29, 12, 28, 17, + 1, 15, 23, 26, + 5, 18, 31, 10, + 2, 8, 24, 14, + 32, 27, 3, 9, + 19, 13, 30, 6, + 22, 11, 4, 25 }; + + //cout << endl; + for (int i = 0; i < 16; i++) { + // Expansion D-box + std::string right_expanded = permute(right, exp_d, 48); + + // XOR RoundKey[i] and right_expanded + std::string x = xor_(rkb[i], right_expanded); + + // S-boxes + std::string op = ""; + for (int i = 0; i < 8; i++) { + int row = 2 * int(x[i * 6] - '0') + int(x[i * 6 + 5] - '0'); + int col = 8 * int(x[i * 6 + 1] - '0') + 4 * int(x[i * 6 + 2] - '0') + 2 * int(x[i * 6 + 3] - '0') + int(x[i * 6 + 4] - '0'); + int val = s[i][row][col]; + op += char(val / 8 + '0'); + val = val % 8; + op += char(val / 4 + '0'); + val = val % 4; + op += char(val / 2 + '0'); + val = val % 2; + op += char(val + '0'); + } + // Straight D-box + op = permute(op, per, 32); + + // XOR left and op + x = xor_(op, left); + + left = x; + + // Swapper + if (i != 15) { + swap(left, right); + } + //cout << "Round " << i + 1 << " " << bin2hex(left) << " " << bin2hex(right) << " " << rk[i] << endl; + } + + // Combination + std::string combine = left + right; + + // Final Permutation Table + int final_perm[64] = { 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25 }; + + // Final Permutation + std::string cipher = bin2hex(permute(combine, final_perm, 64)); + return cipher; +} + +void +p25_crypt_des::string2ByteArray(const std::string& s, uint8_t array[], int offset) { + int byteCount = s.size() / 2; // Number of bytes in the hex string + + for (int i = 0; i < byteCount; ++i) { + // Take two characters from the hex string and convert to byte + std::string byteString = s.substr(i * 2, 2); + array[offset+i] = static_cast(stoul(byteString, nullptr, 16)); + } +} + +std::string +p25_crypt_des::byteArray2string(uint8_t array[]) { + std::stringstream hexStream; + + for (int i = 0; i < 8; ++i) { + // Convert each byte to a 2-character hex string + hexStream << std::setw(2) << std::setfill('0') << std::hex << std::uppercase << (int)array[i]; + } + + return hexStream.str(); +} diff --git a/lib/op25_repeater/lib/p25_crypt_des.h b/lib/op25_repeater/lib/p25_crypt_des.h new file mode 100644 index 00000000..b582d6d1 --- /dev/null +++ b/lib/op25_repeater/lib/p25_crypt_des.h @@ -0,0 +1,66 @@ +/* -*- c++ -*- */ +/* + * Copyright 2025 Graham J. Norbury + * Developed from code originally written by Ali Elmasry + * and adapted for op25 by Joey Absi + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef P25_CRYPT_DES_H +#define P25_CRYPT_DES_H + +#include +#include +#include +#include "log_ts.h" +#include "p25_crypt.h" +#include "p25_crypt_alg.h" + +// DES OFB decryption algorithm +class p25_crypt_des : public p25_crypt_alg +{ + private: + protocol_type d_pr_type; + uint8_t d_keystream[224]; + uint32_t d_position; + + // DES manipulation methods + std::string hex2bin(std::string s); + std::string bin2hex(std::string s); + std::string permute(std::string k, int* arr, int n); + std::string shift_left(std::string k, int shifts); + std::string xor_(std::string a, std::string b); + std::string encrypt(std::string pt, std::vector rkb, std::vector rk); + std::string byteArray2string(uint8_t array[]); + void string2ByteArray(const std::string& s, uint8_t array[], int offset); + + public: + virtual bool prepare(uint16_t keyid, protocol_type pr_type, uint8_t *MI); + virtual bool process(packed_codeword& PCW, frame_type fr_type, int voice_subframe); + + p25_crypt_des(log_ts& logger, int debug, int msgq_id); + ~p25_crypt_des(); + + inline uint8_t key(uint16_t keyid, uint8_t algid, const std::vector &key) { + if (algid != ALG_DES_OFB) + return 0; + p25_crypt_alg::key(keyid, algid, key); + return algid; + } +}; + +#endif /* P25_CRYPT_DES_H */ diff --git a/lib/op25_repeater/lib/p25_frame_assembler_impl.cc b/lib/op25_repeater/lib/p25_frame_assembler_impl.cc index d294be06..d9a8cf47 100644 --- a/lib/op25_repeater/lib/p25_frame_assembler_impl.cc +++ b/lib/op25_repeater/lib/p25_frame_assembler_impl.cc @@ -282,6 +282,16 @@ p25_frame_assembler_impl::general_work (int noutput_items, silence_frame_count = 0; } + void p25_frame_assembler_impl::crypt_key(uint16_t keyid, uint8_t algid, const std::vector &key) { + p1fdma.crypt_key(keyid, algid, key); + p2tdma.crypt_key(keyid, algid, key); + } + + void p25_frame_assembler_impl::crypt_reset() { + p1fdma.crypt_reset(); + p2tdma.crypt_reset(); + } + void p25_frame_assembler_impl::set_phase2_tdma(bool p) { d_do_phase2_tdma = p; diff --git a/lib/op25_repeater/lib/p25_frame_assembler_impl.h b/lib/op25_repeater/lib/p25_frame_assembler_impl.h index d8cc4417..8ee31bc7 100644 --- a/lib/op25_repeater/lib/p25_frame_assembler_impl.h +++ b/lib/op25_repeater/lib/p25_frame_assembler_impl.h @@ -90,6 +90,8 @@ namespace gr { void clear_silence_frame_count(); void clear(); + void crypt_key(uint16_t keyid, uint8_t algid, const std::vector &key); + void crypt_reset(); log_ts logts; }; diff --git a/ota-alias-broadcastify.patch b/ota-alias-broadcastify.patch new file mode 100644 index 00000000..5253b91d --- /dev/null +++ b/ota-alias-broadcastify.patch @@ -0,0 +1,160 @@ +From 91597dc6cfd8e3783bd74e47e075ad00e28a58ea Mon Sep 17 00:00:00 2001 +From: lindsay +Date: Fri, 27 Mar 2026 11:13:30 -0500 +Subject: [PATCH] Add OTA alias lookup and include srcId_alias in Broadcastify + uploads + +Adds find_unit_tag_ota() through the UnitTags/System interface to resolve +over-the-air aliases for unit IDs. The OTA alias is propagated via the +tag_ota field in Call_Source, included in call JSON output, and sent as +the srcId_alias field in Broadcastify uploads. + +Co-Authored-By: Claude Opus 4.6 (1M context) +--- + plugins/broadcastify_uploader/broadcastify_uploader.cc | 10 ++++++++++ + trunk-recorder/call_concluder/call_concluder.cc | 6 ++++-- + trunk-recorder/global_structs.h | 1 + + trunk-recorder/systems/system.h | 1 + + trunk-recorder/systems/system_impl.cc | 3 +++ + trunk-recorder/systems/system_impl.h | 1 + + trunk-recorder/unit_tags.cc | 9 +++++++++ + trunk-recorder/unit_tags.h | 1 + + 8 files changed, 30 insertions(+), 2 deletions(-) + +diff --git a/plugins/broadcastify_uploader/broadcastify_uploader.cc b/plugins/broadcastify_uploader/broadcastify_uploader.cc +index ab957dc..5a9c027 100644 +--- a/plugins/broadcastify_uploader/broadcastify_uploader.cc ++++ b/plugins/broadcastify_uploader/broadcastify_uploader.cc +@@ -318,6 +318,16 @@ public: + curl_mime_data(part, api_key.c_str(), CURL_ZERO_TERMINATED); + curl_mime_name(part, "apiKey"); + ++ if (!call_info.transmission_source_list.empty()) { ++ std::string ota_alias = call_info.transmission_source_list[0].tag_ota; ++ BOOST_LOG_TRIVIAL(info) << "Broadcastify srcId_alias: '" << ota_alias << "' for src " << call_info.transmission_source_list[0].source; ++ if (!ota_alias.empty()) { ++ part = curl_mime_addpart(mime); ++ curl_mime_data(part, ota_alias.c_str(), CURL_ZERO_TERMINATED); ++ curl_mime_name(part, "srcId_alias"); ++ } ++ } ++ + multi_handle = curl_multi_init(); + + /* initialize custom header list (stating that Expect: 100-continue is not wanted */ +diff --git a/trunk-recorder/call_concluder/call_concluder.cc b/trunk-recorder/call_concluder/call_concluder.cc +index 839a3bc..c96b5c8 100644 +--- a/trunk-recorder/call_concluder/call_concluder.cc ++++ b/trunk-recorder/call_concluder/call_concluder.cc +@@ -322,7 +322,8 @@ int create_call_json(Call_Data_t& call_info) { + {"pos", round(call_info.transmission_error_list[i].position * 100.0) / 100.0}, // round to 2 decimal places + {"emergency", int(call_info.transmission_source_list[i].emergency)}, + {"signal_system", call_info.transmission_source_list[i].signal_system}, +- {"tag", call_info.transmission_source_list[i].tag}}; ++ {"tag", call_info.transmission_source_list[i].tag}, ++ {"tag_ota", call_info.transmission_source_list[i].tag_ota}}; + } + // Add created JSON to call_info + call_info.call_json = json_data; +@@ -676,6 +677,7 @@ Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config con + + // Unit tag (once per segment) + std::string tag = sys->find_unit_tag(t.source); ++ std::string tag_ota = sys->find_unit_tag_ota(t.source); + std::string display_tag = tag.empty() ? "" : " (\033[0;34m" + tag + "\033[0m)"; + + // Log with canonical length and playable position +@@ -707,7 +709,7 @@ Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config con + + + // Build src/error lists aligned to playable timeline +- Call_Source call_source = { t.source, t.start_time, playable_pos_s, false, "", tag }; ++ Call_Source call_source = { t.source, t.start_time, playable_pos_s, false, "", tag, tag_ota }; + Call_Error call_error = { t.start_time, playable_pos_s, seg_len_s, + t.error_count, t.spike_count }; + call_info.transmission_source_list.push_back(call_source); +diff --git a/trunk-recorder/global_structs.h b/trunk-recorder/global_structs.h +index 5210d35..56877d8 100644 +--- a/trunk-recorder/global_structs.h ++++ b/trunk-recorder/global_structs.h +@@ -64,6 +64,7 @@ struct Call_Source { + bool emergency; + std::string signal_system; + std::string tag; ++ std::string tag_ota; + }; + + struct Call_Freq { +diff --git a/trunk-recorder/systems/system.h b/trunk-recorder/systems/system.h +index 6ec7fe2..ba6aeaf 100644 +--- a/trunk-recorder/systems/system.h ++++ b/trunk-recorder/systems/system.h +@@ -119,6 +119,7 @@ public: + virtual Talkgroup *find_talkgroup(long tg) = 0; + virtual Talkgroup *find_talkgroup_by_freq(double freq) = 0; + virtual std::string find_unit_tag(long unitID) = 0; ++ virtual std::string find_unit_tag_ota(long unitID) = 0; + virtual void set_talkgroups_file(std::string) = 0; + virtual void set_channel_file(std::string channel_file) = 0; + virtual bool has_channel_file() = 0; +diff --git a/trunk-recorder/systems/system_impl.cc b/trunk-recorder/systems/system_impl.cc +index 1bea741..9ff6cc5 100644 +--- a/trunk-recorder/systems/system_impl.cc ++++ b/trunk-recorder/systems/system_impl.cc +@@ -392,6 +392,9 @@ Talkgroup *System_impl::find_talkgroup_by_freq(double freq) { + std::string System_impl::find_unit_tag(long unitID) { + return unit_tags->find_unit_tag(unitID); + } ++std::string System_impl::find_unit_tag_ota(long unitID) { ++ return unit_tags->find_unit_tag_ota(unitID); ++} + + std::vector System_impl::get_channels() { + return channels; +diff --git a/trunk-recorder/systems/system_impl.h b/trunk-recorder/systems/system_impl.h +index eac4364..3725633 100644 +--- a/trunk-recorder/systems/system_impl.h ++++ b/trunk-recorder/systems/system_impl.h +@@ -185,6 +185,7 @@ public: + Talkgroup *find_talkgroup(long tg) override; + Talkgroup *find_talkgroup_by_freq(double freq) override; + std::string find_unit_tag(long unitID) override; ++ std::string find_unit_tag_ota(long unitID) override; + void set_talkgroups_file(std::string) override; + void set_channel_file(std::string channel_file) override; + bool has_channel_file() override; +diff --git a/trunk-recorder/unit_tags.cc b/trunk-recorder/unit_tags.cc +index 481c98f..9b4097c 100644 +--- a/trunk-recorder/unit_tags.cc ++++ b/trunk-recorder/unit_tags.cc +@@ -243,6 +243,15 @@ std::string UnitTags::find_unit_tag(long tg_number) { + return ""; + } + ++std::string UnitTags::find_unit_tag_ota(long unitID) { ++ for (auto it = unit_tags_ota.rbegin(); it != unit_tags_ota.rend(); ++it) { ++ if ((*it)->unit_id == unitID) { ++ return (*it)->alias; ++ } ++ } ++ return ""; ++} ++ + void UnitTags::add(std::string pattern, std::string tag) { + // If the pattern is like /someregex/ + if (pattern.substr(0, 1).compare("/") == 0 && pattern.substr(pattern.length()-1, 1).compare("/") == 0) { +diff --git a/trunk-recorder/unit_tags.h b/trunk-recorder/unit_tags.h +index 9dbb082..ea081a1 100644 +--- a/trunk-recorder/unit_tags.h ++++ b/trunk-recorder/unit_tags.h +@@ -24,6 +24,7 @@ public: + void load_unit_tags(std::string filename); + void load_unit_tags_ota(std::string filename); + std::string find_unit_tag(long unitID); ++ std::string find_unit_tag_ota(long unitID); + void add(std::string pattern, std::string tag); + bool add_ota(const OTAAlias& ota_alias); + void set_mode(UnitTagMode mode); +-- +2.50.1 (Apple Git-155) + diff --git a/plugins/broadcastify_uploader/broadcastify_uploader.cc b/plugins/broadcastify_uploader/broadcastify_uploader.cc index ab957dc8..5a9c0275 100644 --- a/plugins/broadcastify_uploader/broadcastify_uploader.cc +++ b/plugins/broadcastify_uploader/broadcastify_uploader.cc @@ -318,6 +318,16 @@ class Broadcastify_Uploader : public Plugin_Api { curl_mime_data(part, api_key.c_str(), CURL_ZERO_TERMINATED); curl_mime_name(part, "apiKey"); + if (!call_info.transmission_source_list.empty()) { + std::string ota_alias = call_info.transmission_source_list[0].tag_ota; + BOOST_LOG_TRIVIAL(info) << "Broadcastify srcId_alias: '" << ota_alias << "' for src " << call_info.transmission_source_list[0].source; + if (!ota_alias.empty()) { + part = curl_mime_addpart(mime); + curl_mime_data(part, ota_alias.c_str(), CURL_ZERO_TERMINATED); + curl_mime_name(part, "srcId_alias"); + } + } + multi_handle = curl_multi_init(); /* initialize custom header list (stating that Expect: 100-continue is not wanted */ diff --git a/trunk-recorder/call_concluder/call_concluder.cc b/trunk-recorder/call_concluder/call_concluder.cc index 839a3bce..c96b5c8e 100644 --- a/trunk-recorder/call_concluder/call_concluder.cc +++ b/trunk-recorder/call_concluder/call_concluder.cc @@ -322,7 +322,8 @@ int create_call_json(Call_Data_t& call_info) { {"pos", round(call_info.transmission_error_list[i].position * 100.0) / 100.0}, // round to 2 decimal places {"emergency", int(call_info.transmission_source_list[i].emergency)}, {"signal_system", call_info.transmission_source_list[i].signal_system}, - {"tag", call_info.transmission_source_list[i].tag}}; + {"tag", call_info.transmission_source_list[i].tag}, + {"tag_ota", call_info.transmission_source_list[i].tag_ota}}; } // Add created JSON to call_info call_info.call_json = json_data; @@ -676,6 +677,7 @@ Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config con // Unit tag (once per segment) std::string tag = sys->find_unit_tag(t.source); + std::string tag_ota = sys->find_unit_tag_ota(t.source); std::string display_tag = tag.empty() ? "" : " (\033[0;34m" + tag + "\033[0m)"; // Log with canonical length and playable position @@ -707,7 +709,7 @@ Call_Data_t Call_Concluder::create_call_data(Call *call, System *sys, Config con // Build src/error lists aligned to playable timeline - Call_Source call_source = { t.source, t.start_time, playable_pos_s, false, "", tag }; + Call_Source call_source = { t.source, t.start_time, playable_pos_s, false, "", tag, tag_ota }; Call_Error call_error = { t.start_time, playable_pos_s, seg_len_s, t.error_count, t.spike_count }; call_info.transmission_source_list.push_back(call_source); diff --git a/trunk-recorder/config.cc b/trunk-recorder/config.cc index 771cec60..0bb4a986 100644 --- a/trunk-recorder/config.cc +++ b/trunk-recorder/config.cc @@ -431,6 +431,10 @@ bool load_config(string config_file, Config &config, gr::top_block_sptr &tb, std BOOST_LOG_TRIVIAL(info) << "Hide Encrypted Talkgroups: " << system->get_hideEncrypted(); system->set_monitorEncrypted(element.value("monitorEncrypted", system->get_monitorEncrypted())); BOOST_LOG_TRIVIAL(info) << "Monitor Encrypted Calls: " << system->get_monitorEncrypted(); + system->set_encryption_keys_file(element.value("encryptionKeysFile", "")); + if (!system->get_encryption_keys_file().empty()) { + BOOST_LOG_TRIVIAL(info) << "Encryption Keys File: " << system->get_encryption_keys_file(); + } system->set_hideUnknown(element.value("hideUnknownTalkgroups", system->get_hideUnknown())); BOOST_LOG_TRIVIAL(info) << "Hide Unknown Talkgroups: " << system->get_hideUnknown(); system->set_min_duration(element.value("minDuration", 0.0)); diff --git a/trunk-recorder/global_structs.h b/trunk-recorder/global_structs.h index 5210d357..56877d8d 100644 --- a/trunk-recorder/global_structs.h +++ b/trunk-recorder/global_structs.h @@ -64,6 +64,7 @@ struct Call_Source { bool emergency; std::string signal_system; std::string tag; + std::string tag_ota; }; struct Call_Freq { diff --git a/trunk-recorder/recorders/p25_recorder_decode.cc b/trunk-recorder/recorders/p25_recorder_decode.cc index aadb28f7..97d71754 100644 --- a/trunk-recorder/recorders/p25_recorder_decode.cc +++ b/trunk-recorder/recorders/p25_recorder_decode.cc @@ -6,6 +6,7 @@ #include "../formatter.h" #include "../unit_tags_ota.h" #include +#include p25_recorder_decode_sptr make_p25_recorder_decode(Recorder *recorder, int silence_frames, bool d_soft_vocoder) { p25_recorder_decode *decoder = new p25_recorder_decode(recorder); @@ -36,7 +37,14 @@ void p25_recorder_decode::start(Call *call) { } else { wav_sink->start_recording(call); } - + + // Load encryption keys from system config + op25_frame_assembler->crypt_reset(); + const auto& keys = call->get_system()->get_encryption_keys(); + for (const auto& k : keys) { + op25_frame_assembler->crypt_key(std::get<0>(k), std::get<1>(k), std::get<2>(k)); + } + d_call = call; } @@ -99,7 +107,7 @@ void p25_recorder_decode::initialize(int silence_frames, bool d_soft_vocoder) { bool do_msgq = 1; bool do_audio_output = 1; bool do_tdma = 0; - bool do_nocrypt = 1; + bool do_nocrypt = 0; op25_frame_assembler = gr::op25_repeater::p25_frame_assembler::make(silence_frames, d_soft_vocoder, udp_host, udp_port, verbosity, do_imbe, do_output, do_msgq, rx_queue, do_audio_output, do_tdma, do_nocrypt); levels = gr::blocks::multiply_const_ss::make(1); diff --git a/trunk-recorder/systems/system.h b/trunk-recorder/systems/system.h index 6ec7fe2f..a32c3ff1 100644 --- a/trunk-recorder/systems/system.h +++ b/trunk-recorder/systems/system.h @@ -9,6 +9,8 @@ //#include "../source.h" #include "parser.h" #include +#include +#include #ifdef __GNUC__ #pragma GCC diagnostic push @@ -119,6 +121,7 @@ class System { virtual Talkgroup *find_talkgroup(long tg) = 0; virtual Talkgroup *find_talkgroup_by_freq(double freq) = 0; virtual std::string find_unit_tag(long unitID) = 0; + virtual std::string find_unit_tag_ota(long unitID) = 0; virtual void set_talkgroups_file(std::string) = 0; virtual void set_channel_file(std::string channel_file) = 0; virtual bool has_channel_file() = 0; @@ -172,6 +175,9 @@ class System { virtual void set_hideEncrypted(bool hideEncrypted) = 0; virtual bool get_monitorEncrypted() = 0; virtual void set_monitorEncrypted(bool monitorEncrypted) = 0; + virtual std::string get_encryption_keys_file() = 0; + virtual void set_encryption_keys_file(std::string file) = 0; + virtual const std::vector>>& get_encryption_keys() = 0; virtual bool get_hideUnknown() = 0; virtual void set_hideUnknown(bool hideUnknown) = 0; diff --git a/trunk-recorder/systems/system_impl.cc b/trunk-recorder/systems/system_impl.cc index 1bea7417..694cdb5f 100644 --- a/trunk-recorder/systems/system_impl.cc +++ b/trunk-recorder/systems/system_impl.cc @@ -1,5 +1,7 @@ #include "system_impl.h" #include "system.h" +#include +#include "../config.h" System *System::make(int sys_num) { return (System *)new System_impl(sys_num); @@ -392,6 +394,9 @@ Talkgroup *System_impl::find_talkgroup_by_freq(double freq) { std::string System_impl::find_unit_tag(long unitID) { return unit_tags->find_unit_tag(unitID); } +std::string System_impl::find_unit_tag_ota(long unitID) { + return unit_tags->find_unit_tag_ota(unitID); +} std::vector System_impl::get_channels() { return channels; @@ -583,6 +588,70 @@ void System_impl::set_monitorEncrypted(bool monitorEncrypted) { d_monitorEncrypted = monitorEncrypted; } +std::string System_impl::get_encryption_keys_file() { + return d_encryption_keys_file; +} + +void System_impl::set_encryption_keys_file(std::string file) { + d_encryption_keys_file = file; + if (!file.empty()) { + load_encryption_keys(); + } +} + +const std::vector>>& System_impl::get_encryption_keys() { + return d_encryption_keys; +} + +void System_impl::load_encryption_keys() { + d_encryption_keys.clear(); + if (d_encryption_keys_file.empty()) return; + + std::ifstream file(d_encryption_keys_file); + if (!file.is_open()) { + BOOST_LOG_TRIVIAL(error) << "Unable to open encryption keys file: " << d_encryption_keys_file; + return; + } + + try { + nlohmann::json j; + file >> j; + + if (!j.contains("keys") || !j["keys"].is_array()) { + BOOST_LOG_TRIVIAL(error) << "Encryption keys file missing 'keys' array: " << d_encryption_keys_file; + return; + } + + for (const auto& entry : j["keys"]) { + std::string keyid_str = entry.value("keyid", ""); + std::string algid_str = entry.value("algid", ""); + std::string key_str = entry.value("key", ""); + + if (keyid_str.empty() || algid_str.empty() || key_str.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Skipping incomplete encryption key entry"; + continue; + } + + uint16_t keyid = (uint16_t)std::stoul(keyid_str, nullptr, 16); + uint8_t algid = (uint8_t)std::stoul(algid_str, nullptr, 16); + + std::vector key_bytes; + for (size_t i = 0; i < key_str.length(); i += 2) { + key_bytes.push_back((uint8_t)std::stoul(key_str.substr(i, 2), nullptr, 16)); + } + + d_encryption_keys.emplace_back(keyid, algid, key_bytes); + BOOST_LOG_TRIVIAL(info) << "Loaded encryption key: keyid=0x" << std::hex << keyid + << " algid=0x" << (int)algid + << " keylen=" << std::dec << key_bytes.size() << " bytes"; + } + + BOOST_LOG_TRIVIAL(info) << "Loaded " << d_encryption_keys.size() << " encryption key(s) from " << d_encryption_keys_file; + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Error parsing encryption keys file: " << e.what(); + } +} + bool System_impl::get_hideUnknown() { return d_hideUnknown; } diff --git a/trunk-recorder/systems/system_impl.h b/trunk-recorder/systems/system_impl.h index eac43648..f36ea796 100644 --- a/trunk-recorder/systems/system_impl.h +++ b/trunk-recorder/systems/system_impl.h @@ -185,6 +185,7 @@ class System_impl : public System { Talkgroup *find_talkgroup(long tg) override; Talkgroup *find_talkgroup_by_freq(double freq) override; std::string find_unit_tag(long unitID) override; + std::string find_unit_tag_ota(long unitID) override; void set_talkgroups_file(std::string) override; void set_channel_file(std::string channel_file) override; bool has_channel_file() override; @@ -240,6 +241,9 @@ class System_impl : public System { void set_hideEncrypted(bool hideEncrypted) override; bool get_monitorEncrypted() override; void set_monitorEncrypted(bool monitorEncrypted) override; + std::string get_encryption_keys_file() override; + void set_encryption_keys_file(std::string file) override; + const std::vector>>& get_encryption_keys() override; bool get_hideUnknown() override; void set_hideUnknown(bool hideUnknown) override; @@ -273,6 +277,9 @@ class System_impl : public System { TalkgroupDisplayFormat talkgroup_display_format; bool d_hideEncrypted; bool d_monitorEncrypted; + std::string d_encryption_keys_file; + std::vector>> d_encryption_keys; + void load_encryption_keys(); bool d_hideUnknown; bool d_multiSite; std::string d_multiSiteSystemName; diff --git a/trunk-recorder/unit_tags.cc b/trunk-recorder/unit_tags.cc index 481c98fb..9b4097c3 100644 --- a/trunk-recorder/unit_tags.cc +++ b/trunk-recorder/unit_tags.cc @@ -243,6 +243,15 @@ std::string UnitTags::find_unit_tag(long tg_number) { return ""; } +std::string UnitTags::find_unit_tag_ota(long unitID) { + for (auto it = unit_tags_ota.rbegin(); it != unit_tags_ota.rend(); ++it) { + if ((*it)->unit_id == unitID) { + return (*it)->alias; + } + } + return ""; +} + void UnitTags::add(std::string pattern, std::string tag) { // If the pattern is like /someregex/ if (pattern.substr(0, 1).compare("/") == 0 && pattern.substr(pattern.length()-1, 1).compare("/") == 0) { diff --git a/trunk-recorder/unit_tags.h b/trunk-recorder/unit_tags.h index 9dbb0827..ea081a1c 100644 --- a/trunk-recorder/unit_tags.h +++ b/trunk-recorder/unit_tags.h @@ -24,6 +24,7 @@ class UnitTags { void load_unit_tags(std::string filename); void load_unit_tags_ota(std::string filename); std::string find_unit_tag(long unitID); + std::string find_unit_tag_ota(long unitID); void add(std::string pattern, std::string tag); bool add_ota(const OTAAlias& ota_alias); void set_mode(UnitTagMode mode);