From 82f0c0c37521b683a12c983c85cae46d31cd0700 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 17 Jul 2026 13:42:22 -0400 Subject: [PATCH] implement support to encrypt any TEK data with a preshared key; --- configs/bridge-config.example.yml | 5 ++ configs/config.example.yml | 5 ++ configs/fne-config.example.yml | 6 ++ configs/patch-config.example.yml | 5 ++ src/bridge/HostBridge.cpp | 44 +++++++++++ src/common/network/BaseNetwork.h | 3 + src/common/network/Network.cpp | 99 +++++++++++++++++++++++ src/common/network/Network.h | 11 +++ src/common/p25/P25Defines.h | 4 + src/fne/network/PeerNetwork.cpp | 3 + src/fne/network/TrafficNetwork.cpp | 110 +++++++++++++++++++++++--- src/fne/network/TrafficNetwork.h | 6 +- src/host/Host.Config.cpp | 43 ++++++++++ src/patch/HostPatch.cpp | 43 ++++++++++ tests/crypto/AES_Crypto_BlockTest.cpp | 66 ++++++++++++++++ 15 files changed, 443 insertions(+), 10 deletions(-) create mode 100644 tests/crypto/AES_Crypto_BlockTest.cpp diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 4d57001a..e7019637 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -70,6 +70,11 @@ network: # 0 - 9, A - F.) presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # AES-256 32-byte Preshared Key for peer-based key requests + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + kmfPresharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # Flag indicating whether or not the host diagnostic log will be sent to the network. allowDiagnosticTransfer: true diff --git a/configs/config.example.yml b/configs/config.example.yml index eaccdd29..fd3d1ac3 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -98,6 +98,11 @@ network: # 0 - 9, A - F.) presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # AES-256 32-byte Preshared Key for peer-based key requests + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + kmfPresharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # Maximum allowable DMR network jitter. jitter: 360 diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 82fcab48..468e0043 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -184,6 +184,12 @@ master: # Flag indicating whether or not P25 OTAR KMF services will allow RID 0 (broadcast) key requests. # NOTE: This is used for peer key requests (NOT OTAR REQUESTS). kmfAllowRID0: false + # Flag indicating whether or not P25 OTAR KMF services will encrypt peer-based key requests. + kmfEncKeyRequest: false + # AES-256 32-byte Preshared Key for peer-based key requests + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + kmfPresharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" # Flag indicating whether or not verbose debug logging for P25 OTAR KMF services is enabled. kmfDebug: false diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index 41a497f5..fbf3849b 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -70,6 +70,11 @@ network: # 0 - 9, A - F.) presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # AES-256 32-byte Preshared Key for peer-based key requests + # (This field *must* be 32 hex bytes in length or 64 characters + # 0 - 9, A - F.) + kmfPresharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" + # Flag indicating whether or not the host diagnostic log will be sent to the network. allowDiagnosticTransfer: true diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index afb4ca73..731148bf 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -1309,6 +1309,44 @@ bool HostBridge::createNetwork() } } + bool kmfEncrypted = false; + uint8_t kmfPresharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + ::memset(kmfPresharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + // scope is intentional + { + std::string key = networkConf["kmfPresharedKey"].as(); + 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 KMF 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(); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = {keyPtr[0], keyPtr[1], 0}; + kmfPresharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + + kmfEncrypted = true; + } + else { + LogWarning(LOG_HOST, "Invalid characters in the KMF preshared encryption key. Encryption disabled."); + kmfEncrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid KMF preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + kmfEncrypted = false; + } + } + } + if (id > 999999999U) { ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); return false; @@ -1324,6 +1362,8 @@ bool HostBridge::createNetwork() LogInfo(" Local: random"); LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); + LogInfo(" KMF Encrypted: %s", kmfEncrypted ? "yes" : "no"); + LogInfo(" Source ID: %u", m_srcId); LogInfo(" Destination ID: %u", m_dstId); LogInfo(" DMR Slot: %u", m_slot); @@ -1371,6 +1411,10 @@ bool HostBridge::createNetwork() m_network->setPresharedKey(presharedKey); } + if (kmfEncrypted) { + m_network->setKMFPresharedKey(kmfPresharedKey); + } + m_network->enable(true); bool ret = m_network->open(); if (!ret) { diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index e828b837..84cad52c 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -98,6 +98,9 @@ namespace network const uint32_t HA_PARAMS_ENTRY_LEN = 20U; + /** @brief KMM Decryption Instruction - Encrypted Peer Key Response */ + const uint8_t KMM_DECRYPT_PEER_ENC = 0x80U; + /** * @brief Network Peer Connection Status * @ingroup network_core diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index 9fe9b3e8..f0281270 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -11,6 +11,7 @@ #include "common/edac/SHA256.h" #include "common/p25/kmm/KMMFactory.h" #include "common/json/json.h" +#include "common/AESCrypto.h" #include "common/Log.h" #include "common/Utils.h" #include "network/Network.h" @@ -62,6 +63,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_ridLookup(nullptr), m_tidLookup(nullptr), m_salt(nullptr), + m_kmfPresharedKey(nullptr), m_retryTimer(1000U, DEFAULT_RETRY_TIME), m_retryCount(0U), m_maxRetryCount(MAX_RETRY_BEFORE_RECONNECT), @@ -76,6 +78,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_promiscuousPeer(false), m_userHandleProtocol(false), m_neverDisableOnACLNAK(false), + m_passKeysWithNoPresharedKey(false), m_peerConnectedCallback(nullptr), m_peerDisconnectedCallback(nullptr), m_dmrInCallCallback(nullptr), @@ -109,6 +112,10 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, Network::~Network() { + if (m_kmfPresharedKey != nullptr) { + delete[] m_kmfPresharedKey; + m_kmfPresharedKey = nullptr; + } delete[] m_salt; delete[] m_rxDMRStreamId; delete[] m_rxP25P2StreamId; @@ -230,6 +237,14 @@ void Network::setPresharedKey(const uint8_t* presharedKey) m_socket->setPresharedKey(presharedKey); } +/* Sets endpoint preshared encryption key. */ + +void Network::setKMFPresharedKey(const uint8_t* presharedKey) +{ + m_kmfPresharedKey = new uint8_t[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + memcpy(m_kmfPresharedKey, presharedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); +} + /* Updates the timer by the passed number of milliseconds. */ void Network::clock(uint32_t ms) @@ -1103,6 +1118,50 @@ void Network::clock(uint32_t ms) LogInfoEx(LOG_NET, "PEER %u, master reported enc. key, algId = $%02X, kID = $%04X", m_peerId, ks.algId(), ki.kId()); + if (!m_passKeysWithNoPresharedKey && (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC)) { + // if the to decrypt the key with a preshared key, do that now + if (m_kmfPresharedKey != nullptr) { + uint8_t encryptedKey[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(encryptedKey, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + ki.getKey(encryptedKey); + + uint8_t* key = nullptr; + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + key = aes.decryptECB(encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, m_kmfPresharedKey); + + uint32_t keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + switch (ks.algId()) { + case P25DEF::ALGO_DES: + keyLength = P25DEF::DES_ENC_KEY_LENGTH_BYTES; + break; + case P25DEF::ALGO_ARC4: + keyLength = P25DEF::ARC4_ENC_KEY_LENGTH_BYTES; + break; + + case P25DEF::ALGO_AES_256: + break; + default: + LogWarning(LOG_NET, "PEER %u, unknown algorithm ID $%02X, unable to determine key length", m_peerId, ks.algId()); + break; + } + + if (m_debug) + Utils::dump(1U, "Network::clock(), Key", key, keyLength); + + if (key != nullptr) { + ki.setKey(key, keyLength); + ks.keyLength(keyLength); + delete[] key; + } + } else { + if (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC) { + LogInfoEx(LOG_NET, "PEER %u, received encrypted enc. key, but no preshared key available, algId = $%02X, kID = $%04X", m_peerId, + ks.algId(), ki.kId()); + break; + } + } + } + // fire off key response callback if we have one if (m_keyRespCallback != nullptr) { m_keyRespCallback(ki, ks.algId(), ks.keyLength()); @@ -1270,6 +1329,46 @@ void Network::clock(uint32_t ms) LogInfoEx(LOG_NET, "PEER %u, master reported LLA enc. key, algId = $%02X, kID = $%04X", m_peerId, ks.algId(), ki.kId()); + if (!m_passKeysWithNoPresharedKey && (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC)) { + // if the to decrypt the key with a preshared key, do that now + if (m_kmfPresharedKey != nullptr) { + uint8_t encryptedKey[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(encryptedKey, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + ki.getKey(encryptedKey); + + uint8_t* key = nullptr; + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + key = aes.decryptECB(encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, m_kmfPresharedKey); + + uint32_t keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + switch (ks.algId()) { + case P25DEF::ALGO_AES_128: + keyLength = P25DEF::AES_128_ENC_KEY_LENGTH_BYTES; + break; + + case P25DEF::ALGO_AES_256: + default: + LogWarning(LOG_NET, "PEER %u, unknown algorithm ID $%02X, unable to determine key length", m_peerId, ks.algId()); + break; + } + + if (m_debug) + Utils::dump(1U, "Network::clock(), Key", key, keyLength); + + if (key != nullptr) { + ki.setKey(key, keyLength); + ks.keyLength(keyLength); + delete[] key; + } + } else { + if (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC) { + LogInfoEx(LOG_NET, "PEER %u, received encrypted LLA enc. key, but no preshared key available, algId = $%02X, kID = $%04X", m_peerId, + ks.algId(), ki.kId()); + break; + } + } + } + // fire off key response callback if we have one if (m_llaKeyRespCallback != nullptr) { m_llaKeyRespCallback(modifyKey->getDstLLId(), ki, ks.keyLength()); diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 9709c0fd..2ea7cc15 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -219,6 +219,11 @@ namespace network * @param presharedKey Encryption preshared key for networking. */ void setPresharedKey(const uint8_t* presharedKey); + /** + * @brief Sets endpoint preshared encryption key for peer key requets. + * @param presharedKey Encryption preshared key for networking. + */ + void setKMFPresharedKey(const uint8_t* presharedKey); /** * @brief Updates the timer by the passed number of milliseconds. @@ -329,6 +334,8 @@ namespace network uint8_t* m_salt; + uint8_t* m_kmfPresharedKey; + Timer m_retryTimer; uint8_t m_retryCount; uint8_t m_maxRetryCount; @@ -364,6 +371,10 @@ namespace network * @brief Flag indicating this peer will not disable networking services on a master ACL NAK. */ bool m_neverDisableOnACLNAK; + /** + * @brief Flag indicating this peer will pass encryption keys without a preshared key set. + */ + bool m_passKeysWithNoPresharedKey; /** * @brief Peer Connected Function Callback. diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index a483307f..4a5277a2 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -170,6 +170,10 @@ namespace p25 const uint8_t AUTH_KEY_LENGTH_BYTES = 16U; const uint8_t MAX_ENC_KEY_LENGTH_BYTES = 32U; + const uint8_t AES_256_ENC_KEY_LENGTH_BYTES = 32U; + const uint8_t AES_128_ENC_KEY_LENGTH_BYTES = 16U; + const uint8_t DES_ENC_KEY_LENGTH_BYTES = 8U; + const uint8_t ARC4_ENC_KEY_LENGTH_BYTES = 5U; const uint8_t MAX_WRAPPED_ENC_KEY_LENGTH_BYTES = 40U; const uint8_t KMM_AES_MAC_LENGTH = 8U; const uint8_t KMM_DES_MAC_LENGTH = 4U; diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index fde8e86c..8a087717 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -71,6 +71,9 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc // never disable peer network services on ACL NAK from master m_neverDisableOnACLNAK = true; + // allow passing encryption keys without a preshared key set + m_passKeysWithNoPresharedKey = true; + // FNE peer network manually handle protocol packets m_userHandleProtocol = true; diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index d0e91bb8..71560421 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.cpp @@ -12,6 +12,7 @@ #include "common/p25/kmm/KMMFactory.h" #include "common/json/json.h" #include "common/zlib/Compression.h" +#include "common/AESCrypto.h" #include "common/Log.h" #include "common/StopWatch.h" #include "common/Utils.h" @@ -98,6 +99,7 @@ TrafficNetwork::TrafficNetwork(HostFNE* host, const std::string& address, uint16 m_address(address), m_port(port), m_password(password), + m_encryptedTrafficConn(false), m_isReplica(false), m_dmrEnabled(dmr), m_p25Enabled(p25), @@ -110,6 +112,8 @@ TrafficNetwork::TrafficNetwork(HostFNE* host, const std::string& address, uint16 m_parrotOverrideSrcId(0U), m_kmfServicesEnabled(false), m_kmfAllowRID0(false), + m_kmfEncKeyRequest(false), + m_kmfPresharedKey(nullptr), m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), @@ -313,6 +317,43 @@ void TrafficNetwork::setOptions(yaml::Node& conf, bool printOptions) #endif // ENABLE_SSL m_kmfAllowRID0 = conf["kmfAllowRID0"].as(false); + // scope is intentional + { + bool encrypted = conf["kmfEncKeyRequest"].as(false); + std::string key = conf["kmfPresharedKey"].as(); + 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 KMF 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(); + m_kmfPresharedKey = new uint8_t[AES_WRAPPED_PCKT_KEY_LEN]; + ::memset(m_kmfPresharedKey, 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}; + m_kmfPresharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + } + else { + LogWarning(LOG_HOST, "Invalid characters in the KMF preshared encryption key. Encryption disabled."); + encrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid KMF preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + encrypted = false; + } + } + + m_kmfEncKeyRequest = encrypted; + } + m_callCollisionTimeout = conf["callCollisionTimeout"].as(5U); m_restrictGrantToAffOnly = conf["restrictGrantToAffiliatedOnly"].as(false); @@ -447,6 +488,10 @@ void TrafficNetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" P25 OTAR KMF Listening Address: %s", m_address.c_str()); LogInfo(" P25 OTAR KMF Listening Port: %u", kmfOtarPort); LogInfo(" P25 KMF Allow RID 0 Requests: %s", m_kmfAllowRID0 ? "yes" : "no"); + LogInfo(" P25 KMF Peer Request Encrypted: %s", m_kmfEncKeyRequest ? "yes" : "no"); + if (!m_encryptedTrafficConn && !m_kmfEncKeyRequest) { + LogWarning(LOG_MASTER, "Peers can make key requests, but the encrypted traffic connection is not enabled and KMF requests are not encrypted! Key requests will be sent in the clear."); + } LogInfo(" High Availability Enabled: %s", m_haEnabled ? "yes" : "no"); if (m_haEnabled) { LogInfo(" Advertised HA WAN IP: %s", m_advertisedHAAddress.c_str()); @@ -471,6 +516,10 @@ void TrafficNetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::Talk void TrafficNetwork::setPresharedKey(const uint8_t* presharedKey) { + if (presharedKey != nullptr) { + m_encryptedTrafficConn = true; + } + m_socket->setPresharedKey(presharedKey); } @@ -1920,12 +1969,32 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) LogInfoEx(LOG_MASTER, "PEER %u (%s) local enc. key, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), modifyKey->getAlgId(), modifyKey->getKId()); + // if configured to encrypt the key with a preshared key, do that now + if (network->m_kmfEncKeyRequest) { + uint8_t* encryptedKey = nullptr; + if (network->m_kmfPresharedKey != nullptr) { + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + encryptedKey = aes.encryptECB(key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); + + if (encryptedKey != nullptr) { + ::memcpy(key, encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + delete[] encryptedKey; + } + } + + if (network->m_debug) { + LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); + Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Encrypted Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + } + // build response buffer uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); KMMModifyKey modifyKeyRsp = KMMModifyKey(); - modifyKeyRsp.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + modifyKeyRsp.setDecryptInfoFmt(network->m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); modifyKeyRsp.setAlgId(modifyKey->getAlgId()); modifyKeyRsp.setKId(0U); @@ -2024,10 +2093,11 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) { KMMModifyKey* modifyKey = static_cast(frame.get()); - LogDebugEx(LOG_MASTER, "TrafficNetwork::taskNetworkRx()", "PEER %u (%s) LLA enc. key request received, dstLLId = %u, algId = %u, kId = %u", peerId, connection->identWithQualifier().c_str(), modifyKey->getDstLLId(), modifyKey->getAlgId(), modifyKey->getKId()); - if (modifyKey->getAlgId() == ALGO_AES_128 && modifyKey->getDstLLId() > 0U) { - LogDebugEx(LOG_MASTER, "TrafficNetwork::taskNetworkRx()", "PEER %u (%s) LLA enc. key request received", peerId, connection->identWithQualifier().c_str()); + if (network->m_debug) + LogDebugEx(LOG_MASTER, "TrafficNetwork::taskNetworkRx()", "PEER %u (%s) LLA enc. key request received, dstLLId = %u, algId = %u, kId = %u", peerId, connection->identWithQualifier().c_str(), + modifyKey->getDstLLId(), modifyKey->getAlgId(), modifyKey->getKId()); + uint32_t requestingRid = modifyKey->getDstLLId(); LogInfoEx(LOG_MASTER, "PEER %u (%s) requested LLA enc. key, rsi = %u", peerId, connection->identWithQualifier().c_str(), @@ -2039,20 +2109,40 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); uint8_t keyLength = keyItem.getKey(key); - //if (network->m_debug) { + if (network->m_debug) { LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - //} + } LogInfoEx(LOG_MASTER, "PEER %u (%s) local enc. key, algId = $%02X, rsi = %u", peerId, connection->identWithQualifier().c_str(), modifyKey->getAlgId(), requestingRid); + // if configured to encrypt the key with a preshared key, do that now + if (network->m_kmfEncKeyRequest) { + uint8_t* encryptedKey = nullptr; + if (network->m_kmfPresharedKey != nullptr) { + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + encryptedKey = aes.encryptECB(key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); + + if (encryptedKey != nullptr) { + ::memcpy(key, encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + delete[] encryptedKey; + } + } + + if (network->m_debug) { + LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); + Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Encrypted Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + } + // build response buffer uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); KMMModifyKey modifyKeyRsp = KMMModifyKey(); - modifyKeyRsp.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + modifyKeyRsp.setDecryptInfoFmt(network->m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); modifyKeyRsp.setAlgId(modifyKey->getAlgId()); modifyKeyRsp.setKId(0U); modifyKeyRsp.setSrcLLId(WUID_FNE); @@ -3620,7 +3710,7 @@ void TrafficNetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); KMMModifyKey modifyKeyRsp = KMMModifyKey(); - modifyKeyRsp.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + modifyKeyRsp.setDecryptInfoFmt(m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); modifyKeyRsp.setAlgId(algId); modifyKeyRsp.setKId(0U); @@ -3688,7 +3778,7 @@ void TrafficNetwork::processLLAResponse(uint32_t srcId, p25::kmm::KeyItem* rspKi ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); KMMModifyKey modifyKeyRsp = KMMModifyKey(); - modifyKeyRsp.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + modifyKeyRsp.setDecryptInfoFmt(m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); modifyKeyRsp.setAlgId(ALGO_AES_128); modifyKeyRsp.setKId(0U); modifyKeyRsp.setSrcLLId(WUID_FNE); @@ -3714,6 +3804,8 @@ void TrafficNetwork::processLLAResponse(uint32_t srcId, p25::kmm::KeyItem* rspKi RTP_END_OF_CALL_SEQ, createStreamId()); peersToRemove.push_back(peerId); + } else { + LogError(LOG_PEER, "upstream master LLA enc. key, peerId = %u, requestingRSI = %u, rsi = %u -- mismatch!", entry.first, requestingRid, srcId); } } diff --git a/src/fne/network/TrafficNetwork.h b/src/fne/network/TrafficNetwork.h index 9bd4bdf4..2887f622 100644 --- a/src/fne/network/TrafficNetwork.h +++ b/src/fne/network/TrafficNetwork.h @@ -316,6 +316,8 @@ namespace network std::string m_password; + bool m_encryptedTrafficConn; + bool m_isReplica; bool m_dmrEnabled; @@ -331,6 +333,8 @@ namespace network bool m_kmfServicesEnabled; bool m_kmfAllowRID0; + bool m_kmfEncKeyRequest; + uint8_t* m_kmfPresharedKey; lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; @@ -351,7 +355,7 @@ namespace network static std::timed_mutex s_keyQueueMutex; std::unordered_map m_peerReplicaKeyQueue; static std::timed_mutex s_llaKeyQueueMutex; - std::unordered_map m_peerReplicaLLAKeyQueue; + std::unordered_map m_peerReplicaLLAKeyQueue; fne_lookups::AffiliationLookup* m_globalAff; diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 7a995e82..01b96b1e 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -907,6 +907,44 @@ bool Host::createNetwork() } } + bool kmfEncrypted = false; + uint8_t kmfPresharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + ::memset(kmfPresharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + // scope is intentional + { + std::string key = networkConf["kmfPresharedKey"].as(); + 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 KMF 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(); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = {keyPtr[0], keyPtr[1], 0}; + kmfPresharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + + kmfEncrypted = true; + } + else { + LogWarning(LOG_HOST, "Invalid characters in the KMF preshared encryption key. Encryption disabled."); + kmfEncrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid KMF preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + kmfEncrypted = false; + } + } + } + if (id > 999999999U) { ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); return false; @@ -959,6 +997,7 @@ bool Host::createNetwork() LogInfo(" Save Network Lookups: %s", saveLookup ? "yes" : "no"); LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); + LogInfo(" KMF Encrypted: %s", kmfEncrypted ? "yes" : "no"); if (packetDump) { LogInfo(" Packet Dump: yes"); @@ -1006,6 +1045,10 @@ bool Host::createNetwork() m_network->setPresharedKey(presharedKey); } + if (kmfEncrypted) { + m_network->setKMFPresharedKey(kmfPresharedKey); + } + m_network->enable(true); bool ret = m_network->open(); if (!ret) { diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index 2aa457df..5f1c61ad 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -519,6 +519,44 @@ bool HostPatch::createNetwork() } } + bool kmfEncrypted = false; + uint8_t kmfPresharedKey[AES_WRAPPED_PCKT_KEY_LEN]; + ::memset(kmfPresharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN); + + // scope is intentional + { + std::string key = networkConf["kmfPresharedKey"].as(); + 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 KMF 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(); + + for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) { + char t[4] = {keyPtr[0], keyPtr[1], 0}; + kmfPresharedKey[i] = (uint8_t)::strtoul(t, NULL, 16); + keyPtr += 2 * sizeof(char); + } + + kmfEncrypted = true; + } + else { + LogWarning(LOG_HOST, "Invalid characters in the KMF preshared encryption key. Encryption disabled."); + kmfEncrypted = false; + } + } + else { + LogWarning(LOG_HOST, "Invalid KMF preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + kmfEncrypted = false; + } + } + } + if (id > 999999999U) { ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); return false; @@ -534,6 +572,7 @@ bool HostPatch::createNetwork() LogInfo(" Local: random"); LogInfo(" Encrypted: %s", encrypted ? "yes" : "no"); + LogInfo(" KMF Encrypted: %s", kmfEncrypted ? "yes" : "no"); LogInfo(" Source TGID: %u", m_srcTGId); LogInfo(" Source DMR Slot: %u", m_srcSlot); @@ -587,6 +626,10 @@ bool HostPatch::createNetwork() m_network->setPresharedKey(presharedKey); } + if (kmfEncrypted) { + m_network->setKMFPresharedKey(kmfPresharedKey); + } + m_network->enable(true); bool ret = m_network->open(); if (!ret) { diff --git a/tests/crypto/AES_Crypto_BlockTest.cpp b/tests/crypto/AES_Crypto_BlockTest.cpp new file mode 100644 index 00000000..5e2170f5 --- /dev/null +++ b/tests/crypto/AES_Crypto_BlockTest.cpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * + */ +#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 Block Test", "[aes][crypto_blocktest]") { + bool failed = false; + + INFO("AES Crypto Block 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[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 + }; + + // perform crypto + AES* aes = new AES(AESKeyLength::AES_256); + + Utils::dump(2U, "AES_Crypto_BlockTest, Message", message, 32); + + uint8_t* crypted = aes->encryptECB(message, 32 * sizeof(uint8_t), K); + Utils::dump(2U, "AES_Crypto_BlockTest, Encrypted", crypted, 32); + + uint8_t* decrypted = aes->decryptECB(crypted, 32 * sizeof(uint8_t), K); + Utils::dump(2U, "AES_Crypto_BlockTest, Decrypted", decrypted, 32); + + for (uint32_t i = 0; i < 32U; i++) { + if (decrypted[i] != message[i]) { + ::LogError("T", "AES_Crypto_BlockTest, INVALID AT IDX %d", i); + failed = true; + } + } + + delete aes; + REQUIRE(failed==false); +}