From 8ff7067ecae939cf3901a75667bf3711d1b6d424 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 23 Jan 2024 15:44:00 -0500 Subject: [PATCH] mplement experimental support to AES-256 encrypt the network connection; cleanup some instances where buffers were not being cleaned up properly; cleanup some of the AESCrypto implementation; remove references to some little used compiler macros; --- configs/config.example.yml | 7 ++ configs/fne-config.example.yml | 14 +++ src/common/AESCrypto.cpp | 104 ++++++++------- src/common/AESCrypto.h | 6 +- src/common/Utils.h | 9 +- src/common/dmr/lc/CSBK.cpp | 3 +- src/common/network/FrameQueue.cpp | 2 +- src/common/network/RawFrameQueue.cpp | 2 +- src/common/network/UDPSocket.cpp | 153 ++++++++++++++++++++++- src/common/network/UDPSocket.h | 11 ++ src/common/p25/lc/TDULC.cpp | 3 +- src/common/p25/lc/TSBK.cpp | 3 +- src/fne/HostFNE.cpp | 75 ++++++++++- src/fne/network/FNENetwork.cpp | 8 ++ src/fne/network/FNENetwork.h | 2 + src/host/Host.Config.cpp | 37 ++++++ src/host/network/Network.cpp | 8 ++ src/host/network/Network.h | 2 + src/host/p25/Control.cpp | 2 + src/host/p25/packet/ControlSignaling.cpp | 3 + tests/crypto/AES_Crypto_Test.cpp | 83 ++++++++++++ 21 files changed, 476 insertions(+), 61 deletions(-) create mode 100644 tests/crypto/AES_Crypto_Test.cpp diff --git a/configs/config.example.yml b/configs/config.example.yml index 9b90110d..8a2adecb 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -45,6 +45,13 @@ network: # FNE access password. password: "PASSWORD" + # Flag indicating whether or not host endpoint networking is encrypted. + encrypted: false + # AES-256 32-byte Preshared Key + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # Maximum allowable DMR network jitter. jitter: 360 diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 0b7e7220..0241a455 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -46,6 +46,13 @@ master: # Flag indicating whether or not verbose debug logging is enabled. debug: false + # Flag indicating whether or not master endpoint networking is encrypted. + encrypted: false + # AES-256 32-byte Preshared Key + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # Flag indicating whether or not DMR traffic will be passed. allowDMRTraffic: true # Flag indicating whether or not P25 traffic will be passed. @@ -89,6 +96,13 @@ peers: # Network Peer ID peerId: 9000990 + # Flag indicating whether or not peer endpoint networking is encrypted. + encrypted: false + # AES-256 32-byte Preshared Key + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # rxFrequency: 0 # diff --git a/src/common/AESCrypto.cpp b/src/common/AESCrypto.cpp index 0f5e7f61..944bcd23 100644 --- a/src/common/AESCrypto.cpp +++ b/src/common/AESCrypto.cpp @@ -34,6 +34,8 @@ */ #include "Defines.h" #include "AESCrypto.h" +#include "Log.h" +#include "Utils.h" using namespace crypto; @@ -272,15 +274,15 @@ static const uint8_t INV_CMDS[4][4] = { {14, 11, 13, 9}, {9, 14, 11, 13}, {13, 9 /// AES::AES(const AESKeyLength keyLength) { switch (keyLength) { - case AESKeyLength::AES_128: + case AESKeyLength::AES_128: this->m_Nk = 4; this->m_Nr = 10; break; - case AESKeyLength::AES_192: + case AESKeyLength::AES_192: this->m_Nk = 6; this->m_Nr = 12; break; - case AESKeyLength::AES_256: + case AESKeyLength::AES_256: this->m_Nk = 8; this->m_Nr = 14; break; @@ -294,17 +296,20 @@ AES::AES(const AESKeyLength keyLength) { /// /// /// -uint8_t* AES::encryptECB(const uint8_t in[], uint32_t inLen, const uint8_t key[]) +uint8_t* AES::encryptECB(const uint8_t in[], uint32_t inLen, const uint8_t key[]) { - if (inLen % m_blockBytesLen != 0) { - throw std::length_error("Plaintext length must be divisible by " + std::to_string(m_blockBytesLen)); + if (inLen % BLOCK_BYTES_LEN != 0) { + LogDebug(LOG_HOST, "AES::encryptECB() Plaintext length must be divisible by %u, inLen = %u", BLOCK_BYTES_LEN, inLen); + return nullptr; } uint8_t* out = new uint8_t[inLen]; + ::memset(out, 0x00U, inLen); uint8_t* roundKeys = new uint8_t[4 * AES_NB * (m_Nr + 1)]; + ::memset(roundKeys, 0x00U, 4 * AES_NB * (m_Nr + 1)); keyExpansion(key, roundKeys); - for (uint32_t i = 0; i < inLen; i += m_blockBytesLen) { + for (uint32_t i = 0; i < inLen; i += BLOCK_BYTES_LEN) { encryptBlock(in + i, out + i, roundKeys); } @@ -321,15 +326,18 @@ uint8_t* AES::encryptECB(const uint8_t in[], uint32_t inLen, const uint8_t key[] /// uint8_t* AES::decryptECB(const uint8_t in[], uint32_t inLen, const uint8_t key[]) { - if (inLen % m_blockBytesLen != 0) { - throw std::length_error("Plaintext length must be divisible by " + std::to_string(m_blockBytesLen)); + if (inLen % BLOCK_BYTES_LEN != 0) { + LogDebug(LOG_HOST, "AES::decryptECB() Plaintext length must be divisible by %u, inLen = %u", BLOCK_BYTES_LEN, inLen); + return nullptr; } uint8_t* out = new uint8_t[inLen]; + ::memset(out, 0x00U, inLen); uint8_t* roundKeys = new uint8_t[4 * AES_NB * (m_Nr + 1)]; + ::memset(roundKeys, 0x00U, 4 * AES_NB * (m_Nr + 1)); keyExpansion(key, roundKeys); - for (uint32_t i = 0; i < inLen; i += m_blockBytesLen) { + for (uint32_t i = 0; i < inLen; i += BLOCK_BYTES_LEN) { decryptBlock(in + i, out + i, roundKeys); } @@ -347,20 +355,23 @@ uint8_t* AES::decryptECB(const uint8_t in[], uint32_t inLen, const uint8_t key[] /// uint8_t* AES::encryptCBC(const uint8_t in[], uint32_t inLen, const uint8_t key[], const uint8_t* iv) { - if (inLen % m_blockBytesLen != 0) { - throw std::length_error("Plaintext length must be divisible by " + std::to_string(m_blockBytesLen)); + if (inLen % BLOCK_BYTES_LEN != 0) { + LogDebug(LOG_HOST, "AES::encryptCBC() Plaintext length must be divisible by %u, inLen = %u", BLOCK_BYTES_LEN, inLen); + return nullptr; } uint8_t* out = new uint8_t[inLen]; - uint8_t block[m_blockBytesLen]; + ::memset(out, 0x00U, inLen); + uint8_t block[BLOCK_BYTES_LEN]; uint8_t* roundKeys = new uint8_t[4 * AES_NB * (m_Nr + 1)]; + ::memset(roundKeys, 0x00U, 4 * AES_NB * (m_Nr + 1)); keyExpansion(key, roundKeys); - memcpy(block, iv, m_blockBytesLen); - for (uint32_t i = 0; i < inLen; i += m_blockBytesLen) { - xorBlocks(block, in + i, block, m_blockBytesLen); + memcpy(block, iv, BLOCK_BYTES_LEN); + for (uint32_t i = 0; i < inLen; i += BLOCK_BYTES_LEN) { + xorBlocks(block, in + i, block, BLOCK_BYTES_LEN); encryptBlock(block, out + i, roundKeys); - memcpy(block, out + i, m_blockBytesLen); + memcpy(block, out + i, BLOCK_BYTES_LEN); } delete[] roundKeys; @@ -377,20 +388,23 @@ uint8_t* AES::encryptCBC(const uint8_t in[], uint32_t inLen, const uint8_t key[] /// uint8_t* AES::decryptCBC(const uint8_t in[], uint32_t inLen, const uint8_t key[], const uint8_t *iv) { - if (inLen % m_blockBytesLen != 0) { - throw std::length_error("Plaintext length must be divisible by " + std::to_string(m_blockBytesLen)); + if (inLen % BLOCK_BYTES_LEN != 0) { + LogDebug(LOG_HOST, "AES::decryptCBC() Plaintext length must be divisible by %u, inLen = %u", BLOCK_BYTES_LEN, inLen); + return nullptr; } uint8_t* out = new uint8_t[inLen]; - uint8_t block[m_blockBytesLen]; + ::memset(out, 0x00U, inLen); + uint8_t block[BLOCK_BYTES_LEN]; uint8_t* roundKeys = new uint8_t[4 * AES_NB * (m_Nr + 1)]; + ::memset(roundKeys, 0x00U, 4 * AES_NB * (m_Nr + 1)); keyExpansion(key, roundKeys); - memcpy(block, iv, m_blockBytesLen); - for (uint32_t i = 0; i < inLen; i += m_blockBytesLen) { + memcpy(block, iv, BLOCK_BYTES_LEN); + for (uint32_t i = 0; i < inLen; i += BLOCK_BYTES_LEN) { decryptBlock(in + i, out + i, roundKeys); - xorBlocks(block, out + i, out + i, m_blockBytesLen); - memcpy(block, in + i, m_blockBytesLen); + xorBlocks(block, out + i, out + i, BLOCK_BYTES_LEN); + memcpy(block, in + i, BLOCK_BYTES_LEN); } delete[] roundKeys; @@ -407,21 +421,24 @@ uint8_t* AES::decryptCBC(const uint8_t in[], uint32_t inLen, const uint8_t key[] /// uint8_t* AES::encryptCFB(const uint8_t in[], uint32_t inLen, const uint8_t key[], const uint8_t *iv) { - if (inLen % m_blockBytesLen != 0) { - throw std::length_error("Plaintext length must be divisible by " + std::to_string(m_blockBytesLen)); + if (inLen % BLOCK_BYTES_LEN != 0) { + LogDebug(LOG_HOST, "AES::encryptCFB() Plaintext length must be divisible by %u, inLen = %u", BLOCK_BYTES_LEN, inLen); + return nullptr; } uint8_t* out = new uint8_t[inLen]; - uint8_t block[m_blockBytesLen]; - uint8_t encryptedBlock[m_blockBytesLen]; + ::memset(out, 0x00U, inLen); + uint8_t block[BLOCK_BYTES_LEN]; + uint8_t encryptedBlock[BLOCK_BYTES_LEN]; uint8_t* roundKeys = new uint8_t[4 * AES_NB * (m_Nr + 1)]; + ::memset(roundKeys, 0x00U, 4 * AES_NB * (m_Nr + 1)); keyExpansion(key, roundKeys); - memcpy(block, iv, m_blockBytesLen); - for (uint32_t i = 0; i < inLen; i += m_blockBytesLen) { + memcpy(block, iv, BLOCK_BYTES_LEN); + for (uint32_t i = 0; i < inLen; i += BLOCK_BYTES_LEN) { encryptBlock(block, encryptedBlock, roundKeys); - xorBlocks(in + i, encryptedBlock, out + i, m_blockBytesLen); - memcpy(block, out + i, m_blockBytesLen); + xorBlocks(in + i, encryptedBlock, out + i, BLOCK_BYTES_LEN); + memcpy(block, out + i, BLOCK_BYTES_LEN); } delete[] roundKeys; @@ -438,21 +455,24 @@ uint8_t* AES::encryptCFB(const uint8_t in[], uint32_t inLen, const uint8_t key[] /// uint8_t* AES::decryptCFB(const uint8_t in[], uint32_t inLen, const uint8_t key[], const uint8_t *iv) { - if (inLen % m_blockBytesLen != 0) { - throw std::length_error("Plaintext length must be divisible by " + std::to_string(m_blockBytesLen)); + if (inLen % BLOCK_BYTES_LEN != 0) { + LogDebug(LOG_HOST, "AES::decryptCFB() Plaintext length must be divisible by %u, inLen = %u", BLOCK_BYTES_LEN, inLen); + return nullptr; } uint8_t* out = new uint8_t[inLen]; - uint8_t block[m_blockBytesLen]; - uint8_t encryptedBlock[m_blockBytesLen]; + ::memset(out, 0x00U, inLen); + uint8_t block[BLOCK_BYTES_LEN]; + uint8_t encryptedBlock[BLOCK_BYTES_LEN]; uint8_t* roundKeys = new uint8_t[4 * AES_NB * (m_Nr + 1)]; + ::memset(roundKeys, 0x00U, 4 * AES_NB * (m_Nr + 1)); keyExpansion(key, roundKeys); - memcpy(block, iv, m_blockBytesLen); - for (uint32_t i = 0; i < inLen; i += m_blockBytesLen) { + memcpy(block, iv, BLOCK_BYTES_LEN); + for (uint32_t i = 0; i < inLen; i += BLOCK_BYTES_LEN) { encryptBlock(block, encryptedBlock, roundKeys); - xorBlocks(in + i, encryptedBlock, out + i, m_blockBytesLen); - memcpy(block, in + i, m_blockBytesLen); + xorBlocks(in + i, encryptedBlock, out + i, BLOCK_BYTES_LEN); + memcpy(block, in + i, BLOCK_BYTES_LEN); } delete[] roundKeys; @@ -633,7 +653,7 @@ void AES::xorWords(uint8_t* a, uint8_t* b, uint8_t* c) /// /// /// -void AES::rcon(uint8_t *a, uint32_t n) +void AES::rCon(uint8_t *a, uint32_t n) { uint8_t c = 1; for (uint32_t i = 0; i < n - 1; i++) { @@ -668,7 +688,7 @@ void AES::keyExpansion(const uint8_t key[], uint8_t w[]) { if (i / 4 % m_Nk == 0) { rotWord(temp); subWord(temp); - this->rcon(rcon, i / (m_Nk * 4)); + rCon(rcon, i / (m_Nk * 4)); xorWords(temp, rcon, temp); } else if (m_Nk > 6 && i / 4 % m_Nk == 4) { subWord(temp); diff --git a/src/common/AESCrypto.h b/src/common/AESCrypto.h index 00178316..86edf792 100644 --- a/src/common/AESCrypto.h +++ b/src/common/AESCrypto.h @@ -74,9 +74,9 @@ namespace crypto /// uint8_t* decryptCFB(const uint8_t in[], uint32_t inLen, const uint8_t key[], const uint8_t* iv); - private: - static constexpr uint32_t m_blockBytesLen = 4 * AES_NB * sizeof(uint8_t); + static constexpr uint32_t BLOCK_BYTES_LEN = 4 * AES_NB * sizeof(uint8_t); + private: uint32_t m_Nk; uint32_t m_Nr; @@ -96,7 +96,7 @@ namespace crypto void rotWord(uint8_t* a); void xorWords(uint8_t* a, uint8_t* b, uint8_t* c); - void rcon(uint8_t* a, uint32_t n); + void rCon(uint8_t* a, uint32_t n); void keyExpansion(const uint8_t key[], uint8_t w[]); diff --git a/src/common/Utils.h b/src/common/Utils.h index 7689ee6a..9d58fed2 100644 --- a/src/common/Utils.h +++ b/src/common/Utils.h @@ -173,16 +173,9 @@ inline std::string strtoupper(const std::string value) { #define new_unique(type, ...) std::unique_ptr(new type(__VA_ARGS__)) -/// Creates a named unique buffer. -#define __UNIQUE_BUFFER(name, type, length) \ - std::unique_ptr name = std::unique_ptr(new type[length]); \ - ::memset(name.get(), 0x00U, length); - +/// Unique uint8_t array. typedef std::unique_ptr UInt8Array; -/// Creates a named uint8_t array buffer. -#define __UNIQUE_UINT8_ARRAY(name, length) __UNIQUE_BUFFER(name, uint8_t, length) - // --------------------------------------------------------------------------- // Class Declaration // Implements various helper utilities. diff --git a/src/common/dmr/lc/CSBK.cpp b/src/common/dmr/lc/CSBK.cpp index a178ca7a..ac7b9a42 100644 --- a/src/common/dmr/lc/CSBK.cpp +++ b/src/common/dmr/lc/CSBK.cpp @@ -220,7 +220,8 @@ ulong64_t CSBK::toValue(const uint8_t* payload) /// UInt8Array CSBK::fromValue(const ulong64_t value) { - __UNIQUE_UINT8_ARRAY(payload, DMR_CSBK_LENGTH_BYTES - 4U); + UInt8Array payload = std::unique_ptr(new uint8_t[DMR_CSBK_LENGTH_BYTES - 4U]); + ::memset(payload.get(), 0x00U, DMR_CSBK_LENGTH_BYTES - 4U); // split ulong64_t (8 byte) value into bytes payload[0U] = (uint8_t)((value >> 56) & 0xFFU); diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index 6298667e..0923cabc 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -136,7 +136,7 @@ UInt8Array FrameQueue::read(int& messageLength, sockaddr_storage& address, uint3 // copy message messageLength = _fneHeader.getMessageLength(); - __UNIQUE_UINT8_ARRAY(message, messageLength); + UInt8Array message = std::unique_ptr(new uint8_t[messageLength]); ::memcpy(message.get(), buffer + (RTP_HEADER_LENGTH_BYTES + RTP_EXTENSION_HEADER_LENGTH_BYTES + RTP_FNE_HEADER_LENGTH_BYTES), messageLength); uint16_t calc = edac::CRC::createCRC16(message.get(), messageLength * 8U); diff --git a/src/common/network/RawFrameQueue.cpp b/src/common/network/RawFrameQueue.cpp index 45993292..14e62782 100644 --- a/src/common/network/RawFrameQueue.cpp +++ b/src/common/network/RawFrameQueue.cpp @@ -87,7 +87,7 @@ UInt8Array RawFrameQueue::read(int& messageLength, sockaddr_storage& address, ui // copy message messageLength = length; - __UNIQUE_UINT8_ARRAY(message, length); + UInt8Array message = std::unique_ptr(new uint8_t[length]); ::memcpy(message.get(), buffer, length); return message; diff --git a/src/common/network/UDPSocket.cpp b/src/common/network/UDPSocket.cpp index 343f6499..fbc0d52e 100644 --- a/src/common/network/UDPSocket.cpp +++ b/src/common/network/UDPSocket.cpp @@ -57,8 +57,13 @@ UDPSocket::UDPSocket(const std::string& address, uint16_t port) : m_address_save(address), m_port_save(port), m_isOpen(false), + m_aes(nullptr), + m_isCryptoWrapped(false), + m_presharedKey(nullptr), m_counter(0U) { + m_aes = new crypto::AES(crypto::AESKeyLength::AES_256); + m_presharedKey = new uint8_t[AES_WRAPPED_PCKT_KEY_LEN]; for (int i = 0; i < UDP_SOCKET_MAX; i++) { m_address[i] = ""; m_port[i] = 0U; @@ -75,8 +80,13 @@ UDPSocket::UDPSocket(uint16_t port) : m_address_save(), m_port_save(port), m_isOpen(false), + m_aes(nullptr), + m_isCryptoWrapped(false), + m_presharedKey(nullptr), m_counter(0U) { + m_aes = new crypto::AES(crypto::AESKeyLength::AES_256); + m_presharedKey = new uint8_t[AES_WRAPPED_PCKT_KEY_LEN]; for (int i = 0; i < UDP_SOCKET_MAX; i++) { m_address[i] = ""; m_port[i] = 0U; @@ -90,7 +100,10 @@ UDPSocket::UDPSocket(uint16_t port) : /// UDPSocket::~UDPSocket() { - /* stub */ + if (m_aes != nullptr) + delete m_aes; + if (m_presharedKey != nullptr) + delete[] m_presharedKey; } /// @@ -233,6 +246,36 @@ int UDPSocket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address, return -1; } + // are we crypto wrapped? + if (m_isCryptoWrapped) { + // does the network packet contain the appropriate magic leader? + uint16_t magic = __GET_UINT16B(buffer, 0U); + if (magic == AES_WRAPPED_PCKT_MAGIC) { + uint32_t cryptedLen = (len - 2U) * sizeof(uint8_t); + // Utils::dump(1U, "UDPSocket::read() crypted", buffer + 2U, cryptedLen); + + // decrypt + uint8_t* decrypted = m_aes->decryptECB(buffer + 2U, cryptedLen, m_presharedKey); + + // Utils::dump(1U, "UDPSocket::read() decrypted", decrypted, cryptedLen); + + // finalize, cleanup buffers and replace with new + if (decrypted != nullptr) { + ::memset(buffer, 0x00U, len); + ::memcpy(buffer, decrypted, len - 2U); + + delete[] decrypted; + len -= 2U; + } else { + delete[] decrypted; + return 0; + } + } + else { + return 0; // this will effectively discard packets without the packet magic + } + } + m_counter++; addrLen = size; return len; @@ -254,11 +297,54 @@ bool UDPSocket::write(const uint8_t* buffer, uint32_t length, const sockaddr_sto bool result = false; + UInt8Array out = nullptr; + // are we crypto wrapped? + if (m_isCryptoWrapped) { + uint32_t cryptedLen = length * sizeof(uint8_t); + uint8_t* cryptoBuffer = new uint8_t[length]; + ::memcpy(cryptoBuffer, buffer, length); + + // do we need to pad the original buffer to be block aligned? + if (cryptedLen % crypto::AES::BLOCK_BYTES_LEN != 0) { + uint32_t alignment = crypto::AES::BLOCK_BYTES_LEN - (cryptedLen % crypto::AES::BLOCK_BYTES_LEN); + cryptedLen += alignment; + + // reallocate buffer and copy + cryptoBuffer = new uint8_t[cryptedLen]; + ::memset(cryptoBuffer, 0x00U, cryptedLen); + ::memcpy(cryptoBuffer, buffer, length); + } + + // encrypt + uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey); + + // Utils::dump(1U, "UDPSocket::write() crypted", crypted, cryptedLen); + + // finalize, cleanup buffers and replace with new + out = std::unique_ptr(new uint8_t[cryptedLen + 2U]); + delete[] cryptoBuffer; + if (crypted != nullptr) { + ::memcpy(out.get() + 2U, crypted, cryptedLen); + __SET_UINT16B(AES_WRAPPED_PCKT_MAGIC, out.get(), 0U); + delete[] crypted; + } else { + if (lenWritten != nullptr) { + *lenWritten = -1; + } + + delete[] crypted; + return false; + } + } else { + out = std::unique_ptr(new uint8_t[length]); + ::memcpy(out.get(), buffer, length); + } + for (int i = 0; i < UDP_SOCKET_MAX; i++) { if (m_fd[i] < 0 || m_af[i] != address.ss_family) continue; - ssize_t sent = ::sendto(m_fd[i], (char*)buffer, length, 0, (sockaddr*)& address, addrLen); + ssize_t sent = ::sendto(m_fd[i], (char*)out.get(), length, 0, (sockaddr*)& address, addrLen); if (sent < 0) { LogError(LOG_NET, "Error returned from sendto, err: %d", errno); @@ -321,6 +407,53 @@ bool UDPSocket::write(BufferVector& buffers, int* lenWritten) continue; } + uint32_t length = buffers.at(i)->length; + if (buffers.at(i)->buffer == nullptr) { + LogError(LOG_NET, "discarding buffered message with len = %u, but deleted buffer?", length); + --size; + continue; + } + + // are we crypto wrapped? + if (m_isCryptoWrapped) { + uint32_t cryptedLen = length * sizeof(uint8_t); + uint8_t* cryptoBuffer = buffers.at(i)->buffer; + + // do we need to pad the original buffer to be block aligned? + if (cryptedLen % crypto::AES::BLOCK_BYTES_LEN != 0) { + uint32_t alignment = crypto::AES::BLOCK_BYTES_LEN - (cryptedLen % crypto::AES::BLOCK_BYTES_LEN); + cryptedLen += alignment; + + // reallocate buffer and copy + cryptoBuffer = new uint8_t[cryptedLen]; + ::memset(cryptoBuffer, 0x00U, cryptedLen); + ::memcpy(cryptoBuffer, buffers.at(i)->buffer, length); + } + + // encrypt + uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey); + delete[] cryptoBuffer; + + if (crypted == nullptr) { + --size; + continue; + } + + // Utils::dump(1U, "UDPSocket::write() crypted", crypted, cryptedLen); + + // finalize + uint8_t out[cryptedLen + 2U]; + ::memcpy(out + 2U, crypted, cryptedLen); + __SET_UINT16B(AES_WRAPPED_PCKT_MAGIC, out, 0U); + + // cleanup buffers and replace with new + delete[] crypted; + //delete buffers[i]->buffer; + buffers[i]->buffer = new uint8_t[cryptedLen + 2U]; + ::memcpy(buffers[i]->buffer, out, cryptedLen + 2U); + buffers[i]->length = cryptedLen + 2U; + } + chunks[i].iov_len = buffers.at(i)->length; chunks[i].iov_base = buffers.at(i)->buffer; sent += buffers.at(i)->length; @@ -393,6 +526,22 @@ void UDPSocket::close(const uint32_t index) } } +/// +/// Sets the preshared encryption key. +/// +/// +void UDPSocket::setPresharedKey(const uint8_t* presharedKey) +{ + if (presharedKey != nullptr) { + ::memset(m_presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + ::memcpy(m_presharedKey, presharedKey, AES_WRAPPED_PCKT_KEY_LEN); + m_isCryptoWrapped = true; + } else { + ::memset(m_presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + m_isCryptoWrapped = false; + } +} + /// /// Helper to lookup a hostname and resolve it to an IP address. /// diff --git a/src/common/network/UDPSocket.h b/src/common/network/UDPSocket.h index 7cdcf856..4a1224e6 100644 --- a/src/common/network/UDPSocket.h +++ b/src/common/network/UDPSocket.h @@ -32,6 +32,7 @@ #define __UDP_SOCKET_H__ #include "common/Defines.h" +#include "common/AESCrypto.h" #include #include @@ -50,6 +51,9 @@ #define UDP_SOCKET_MAX 1 #endif +#define AES_WRAPPED_PCKT_MAGIC 0xC0FEU +#define AES_WRAPPED_PCKT_KEY_LEN 32 + enum IPMATCHTYPE { IMT_ADDRESS_AND_PORT, IMT_ADDRESS_ONLY @@ -130,6 +134,9 @@ namespace network /// Closes the UDP socket connection. void close(const uint32_t index); + /// Sets the preshared encryption key. + void setPresharedKey(const uint8_t* presharedKey); + /// Flag indicating the UDP socket(s) are open. bool isOpen() const { return m_isOpen; } @@ -159,6 +166,10 @@ namespace network uint32_t m_af[UDP_SOCKET_MAX]; int m_fd[UDP_SOCKET_MAX]; + crypto::AES* m_aes; + bool m_isCryptoWrapped; + uint8_t* m_presharedKey; + uint32_t m_counter; }; } // namespace network diff --git a/src/common/p25/lc/TDULC.cpp b/src/common/p25/lc/TDULC.cpp index 70ab6b98..e59192c1 100644 --- a/src/common/p25/lc/TDULC.cpp +++ b/src/common/p25/lc/TDULC.cpp @@ -150,7 +150,8 @@ ulong64_t TDULC::toValue(const uint8_t* payload) /// UInt8Array TDULC::fromValue(const ulong64_t value) { - __UNIQUE_UINT8_ARRAY(payload, P25_TDULC_PAYLOAD_LENGTH_BYTES); + UInt8Array payload = std::unique_ptr(new uint8_t[P25_TDULC_PAYLOAD_LENGTH_BYTES]); + ::memset(payload.get(), 0x00U, P25_TDULC_PAYLOAD_LENGTH_BYTES); // split ulong64_t (8 byte) value into bytes payload[0U] = (uint8_t)((value >> 56) & 0xFFU); diff --git a/src/common/p25/lc/TSBK.cpp b/src/common/p25/lc/TSBK.cpp index 8596745a..f4125f24 100644 --- a/src/common/p25/lc/TSBK.cpp +++ b/src/common/p25/lc/TSBK.cpp @@ -195,7 +195,8 @@ ulong64_t TSBK::toValue(const uint8_t* payload) /// UInt8Array TSBK::fromValue(const ulong64_t value) { - __UNIQUE_UINT8_ARRAY(payload, P25_TSBK_LENGTH_BYTES - 4U); + UInt8Array payload = std::unique_ptr(new uint8_t[P25_TSBK_LENGTH_BYTES - 4U]); + ::memset(payload.get(), 0x00U, P25_TSBK_LENGTH_BYTES - 4U); // split ulong64_t (8 byte) value into bytes payload[0U] = (uint8_t)((value >> 56) & 0xFFU); diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index f2673119..cfb1dc60 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -395,6 +395,38 @@ bool HostFNE::createMasterNetwork() bool verbose = masterConf["verbose"].as(false); bool debug = masterConf["debug"].as(false); + bool encrypted = masterConf["encrypted"].as(false); + std::string key = masterConf["presharedKey"].as(); + uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + if (!key.empty()) { + if (key.size() == 32) { + // bryanb: shhhhhhh....dirty nasty hacks + key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs) + LogWarning(LOG_HOST, "Half-length master network preshared encryption key detected, doubling key on itself."); + } + + if (key.size() == 64) { + if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { + const char* keyPtr = key.c_str(); + ::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = {keyPtr[0], keyPtr[1], 0}; + presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + } + else { + LogWarning(LOG_HOST, "Invalid characters in the master network preshared encryption key. Encryption disabled."); + encrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid master network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + encrypted = false; + } + } + if (id > 999999999U) { ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); return false; @@ -421,6 +453,8 @@ bool HostFNE::createMasterNetwork() LogInfo(" Parrot Repeat Delay: %u ms", parrotDelay); LogInfo(" Parrot Grant Demand: %s", parrotGrantDemand ? "yes" : "no"); + LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); + if (verbose) { LogInfo(" Verbose: yes"); } @@ -447,6 +481,10 @@ bool HostFNE::createMasterNetwork() return false; } + if (encrypted) { + m_network->setPresharedKey(presharedKey); + } + return true; } @@ -468,6 +506,38 @@ bool HostFNE::createPeerNetworks() uint32_t id = peerConf["peerId"].as(1001U); bool debug = peerConf["debug"].as(false); + bool encrypted = peerConf["encrypted"].as(false); + std::string key = peerConf["presharedKey"].as(); + uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + if (!key.empty()) { + if (key.size() == 32) { + // bryanb: shhhhhhh....dirty nasty hacks + key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs) + LogWarning(LOG_HOST, "Half-length peer network preshared encryption key detected, doubling key on itself."); + } + + if (key.size() == 64) { + if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { + const char* keyPtr = key.c_str(); + ::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = {keyPtr[0], keyPtr[1], 0}; + presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + } + else { + LogWarning(LOG_HOST, "Invalid characters in the peer network preshared encryption key. Encryption disabled."); + encrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid peer network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + encrypted = false; + } + } + std::string identity = peerConf["identity"].as(); uint32_t rxFrequency = peerConf["rxFrequency"].as(0U); uint32_t txFrequency = peerConf["txFrequency"].as(0U); @@ -475,7 +545,7 @@ bool HostFNE::createPeerNetworks() float longitude = peerConf["longitude"].as(0.0F); std::string location = peerConf["location"].as(); - ::LogInfoEx(LOG_HOST, "Peer ID %u Master Address %s Master Port %u Identity %s Enabled %u", id, masterAddress.c_str(), masterPort, identity.c_str(), enabled); + ::LogInfoEx(LOG_HOST, "Peer ID %u Master Address %s Master Port %u Identity %s Enabled %u Encrypted %u", id, masterAddress.c_str(), masterPort, identity.c_str(), enabled, encrypted); if (id > 999999999U) { ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); @@ -485,6 +555,9 @@ bool HostFNE::createPeerNetworks() // initialize networking network::PeerNetwork* network = new PeerNetwork(masterAddress, masterPort, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, true, true, m_allowActivityTransfer, m_allowDiagnosticTransfer, false); network->setMetadata(identity, rxFrequency, txFrequency, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location); + if (encrypted) { + m_network->setPresharedKey(presharedKey); + } network->enable(enabled); if (enabled) { diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 224855da..c5f72d0f 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -124,6 +124,14 @@ void FNENetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::Talkgrou m_tidLookup = tidLookup; } +/// +/// Sets endpoint preshared encryption key. +/// +void FNENetwork::setPresharedKey(const uint8_t* presharedKey) +{ + m_socket->setPresharedKey(presharedKey); +} + /// /// Updates the timer by the passed number of milliseconds. /// diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 1a1996ce..ff8389d4 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -170,6 +170,8 @@ namespace network /// Sets the instances of the Radio ID and Talkgroup Rules lookup tables. void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup); + /// Sets endpoint preshared encryption key. + void setPresharedKey(const uint8_t* presharedKey); /// Updates the timer by the passed number of milliseconds. void clock(uint32_t ms); diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index b9c3452d..6b60271f 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -686,6 +686,38 @@ bool Host::createNetwork() bool updateLookup = networkConf["updateLookups"].as(false); bool debug = networkConf["debug"].as(false); + bool encrypted = networkConf["encrypted"].as(false); + std::string key = networkConf["presharedKey"].as(); + uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + if (!key.empty()) { + if (key.size() == 32) { + // bryanb: shhhhhhh....dirty nasty hacks + key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs) + LogWarning(LOG_HOST, "Half-length network preshared encryption key detected, doubling key on itself."); + } + + if (key.size() == 64) { + if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { + const char* keyPtr = key.c_str(); + ::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = {keyPtr[0], keyPtr[1], 0}; + presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + } + else { + LogWarning(LOG_HOST, "Invalid characters in the network preshared encryption key. Encryption disabled."); + encrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + encrypted = false; + } + } + if (id > 999999999U) { ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); return false; @@ -722,6 +754,8 @@ bool Host::createNetwork() LogInfo(" Allow Diagnostic Log Transfer: %s", allowDiagnosticTransfer ? "yes" : "no"); LogInfo(" Update Lookups: %s", updateLookup ? "yes" : "no"); + LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); + if (debug) { LogInfo(" Debug: yes"); } @@ -746,6 +780,9 @@ bool Host::createNetwork() if (restApiEnable) { m_network->setRESTAPIData(restApiPassword, restApiPort); } + if (encrypted) { + m_network->setPresharedKey(presharedKey); + } m_network->enable(true); bool ret = m_network->open(); diff --git a/src/host/network/Network.cpp b/src/host/network/Network.cpp index b91f9546..3a875efd 100644 --- a/src/host/network/Network.cpp +++ b/src/host/network/Network.cpp @@ -214,6 +214,14 @@ void Network::setRESTAPIData(const std::string& password, uint16_t port) m_restApiPort = port; } +/// +/// Sets endpoint preshared encryption key. +/// +void Network::setPresharedKey(const uint8_t* presharedKey) +{ + m_socket->setPresharedKey(presharedKey); +} + /// /// Updates the timer by the passed number of milliseconds. /// diff --git a/src/host/network/Network.h b/src/host/network/Network.h index 40144781..fc38ed2a 100644 --- a/src/host/network/Network.h +++ b/src/host/network/Network.h @@ -68,6 +68,8 @@ namespace network uint8_t channelId, uint32_t channelNo, uint32_t power, float latitude, float longitude, int height, const std::string& location); /// Sets REST API configuration settings from the modem. void setRESTAPIData(const std::string& password, uint16_t port); + /// Sets endpoint preshared encryption key. + void setPresharedKey(const uint8_t* presharedKey); /// Updates the timer by the passed number of milliseconds. void clock(uint32_t ms); diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index b9481633..b9007bd4 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1703,5 +1703,7 @@ void Control::generateLLA_AM1_Parameters() LogMessage(LOG_P25, "P25, generated LLA AM1 parameters"); } + // cleanup + delete[] KS; delete aes; } diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index dc6703d3..19940134 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -639,6 +639,9 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetSysId()); } diff --git a/tests/crypto/AES_Crypto_Test.cpp b/tests/crypto/AES_Crypto_Test.cpp new file mode 100644 index 00000000..09fa5898 --- /dev/null +++ b/tests/crypto/AES_Crypto_Test.cpp @@ -0,0 +1,83 @@ +/** +* Digital Voice Modem - Host Software (Test Suite) +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software / Test Suite +* +*/ +/* +* Copyright (C) 2023 Bryan Biedenkapp N2PLL +* +* This program 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 2 of the License, or +* (at your option) any later version. +* +* This program 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 program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "host/Defines.h" +#include "common/AESCrypto.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace crypto; + +#include +#include +#include + +TEST_CASE("AES", "[Crypto Test]") { + SECTION("AES_Crypto_Test") { + bool failed = false; + + INFO("AES Crypto Test"); + + srand((unsigned int)time(NULL)); + + // key (K) + uint8_t K[32] = + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + + // message + uint8_t message[48] = + { + 0x90, 0x56, 0x00, 0x00, 0x2D, 0x75, 0xE6, 0x8D, 0x00, 0x89, 0x69, 0xCF, 0x00, 0xFE, 0x00, 0x04, + 0x4F, 0xC7, 0x60, 0xFF, 0x30, 0x3E, 0x2B, 0xAD, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x08, + 0x52, 0x50, 0x54, 0x4C, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + // perform crypto + AES* aes = new AES(AESKeyLength::AES_256); + + Utils::dump(2U, "AES_Crypto_Test, Message", message, 48); + + uint8_t* crypted = aes->encryptECB(message, 48 * sizeof(uint8_t), K); + Utils::dump(2U, "AES_Crypto_Test, Encrypted", crypted, 48); + + uint8_t* decrypted = aes->decryptECB(crypted, 48 * sizeof(uint8_t), K); + Utils::dump(2U, "AES_Crypto_Test, Decrypted", decrypted, 48); + + for (uint32_t i = 0; i < 48U; i++) { + if (decrypted[i] != message[i]) { + ::LogDebug("T", "AES_Crypto_Test, INVALID AT IDX %d\n", i); + failed = true; + } + } + + delete aes; + REQUIRE(failed==false); + } +}