diff --git a/configs/config.example.yml b/configs/config.example.yml index ee608aa6..26e8da3b 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -526,12 +526,6 @@ system: # RPC access password for voice channel. rpcPassword: "ULTRA-VERY-SECURE-DEFAULT" - secure: - # AES-128 16-byte Key (used for LLA) - # (This field *must* be 16 hex bytes in length or 32 characters - # 0 - 9, A - F.) - key: "000102030405060708090A0B0C0D0E0F" - # DMR Color Code. colorCode: 1 # P25 Network Access Code (NAC). (Rx/Tx) diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index 6c413c15..4ee22542 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -154,6 +154,38 @@ bool BaseNetwork::writeKeyReq(const uint16_t kId, const uint8_t algId, const uin return writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, buffer, modifyKeyCmd.length() + 11U, RTP_END_OF_CALL_SEQ, 0U); } +/* Writes LLA enc. key request to the network. */ + +bool BaseNetwork::writeLLAKeyReq(const uint32_t srcId) +{ + using namespace p25::defines; + using namespace p25::kmm; + + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + KMMModifyKey modifyKeyCmd = KMMModifyKey(); + modifyKeyCmd.setDstLLId(srcId); + modifyKeyCmd.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + modifyKeyCmd.setAlgId(ALGO_AES_128); + modifyKeyCmd.setKId(0U); + + KeysetItem ks = KeysetItem(); + ks.keysetId(0U); + ks.algId(ALGO_AES_128); + ks.keyLength(P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + modifyKeyCmd.setKeysetItem(ks); + + modifyKeyCmd.encode(buffer + 11U); + + //Utils::dump("BaseNetwork::writeLLAKeyReq(), KMM Buffer", buffer, modifyKeyCmd.length() + 11U); + + return writeMaster({ NET_FUNC::KEY_LLA_REQ, NET_SUBFUNC::NOP }, buffer, modifyKeyCmd.length() + 11U, RTP_END_OF_CALL_SEQ, 0U); +} + /* Writes the local activity log to the network. */ bool BaseNetwork::writeActLog(const char* message) diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index b8013519..e828b837 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -466,6 +466,13 @@ namespace network */ bool writeKeyReq(const uint16_t kId, const uint8_t algId, const uint32_t srcId = 0U); + /** + * @brief Writes a LLA enc. key request to the network. + * @param srcId Source Radio ID. + * @returns bool True, if request was sent, otherwise false. + */ + bool writeLLAKeyReq(const uint32_t srcId); + /** * @brief Writes the local activity log to the network. * \code{.unparsed} diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index 8d132f98..e0f02273 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -82,7 +82,8 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_p25InCallCallback(nullptr), m_nxdnInCallCallback(nullptr), m_analogInCallCallback(nullptr), - m_keyRespCallback(nullptr) + m_keyRespCallback(nullptr), + m_llaKeyRespCallback(nullptr) { assert(!address.empty()); assert(port > 0U); @@ -1231,6 +1232,47 @@ void Network::clock(uint32_t ms) } } break; + + + case NET_FUNC::KEY_LLA_RSP: // LLA Enc. Key Response + { + if (m_enabled) { + using namespace p25::kmm; + + std::unique_ptr frame = KMMFactory::create(buffer.get() + 11U); + if (frame == nullptr) { + LogWarning(LOG_NET, "PEER %u, undecodable KMM frame from master", m_peerId); + break; + } + + switch (frame->getMessageId()) { + case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: + { + KMMModifyKey* modifyKey = static_cast(frame.get()); + if (modifyKey->getAlgId() > 0U) { + KeysetItem ks = modifyKey->getKeysetItem(); + if (ks.keys().size() > 0U) { + // fetch first key (a master response should never really send back more then one key) + KeyItem ki = ks.keys()[0]; + LogInfoEx(LOG_NET, "PEER %u, master reported LLA enc. key, algId = $%02X, kID = $%04X", m_peerId, + ks.algId(), ki.kId()); + + // fire off key response callback if we have one + if (m_llaKeyRespCallback != nullptr) { + m_llaKeyRespCallback(modifyKey->getDstLLId(), ki, ks.keyLength()); + } + } + } + } + break; + + default: + break; + } + } + } + break; + case NET_FUNC::MST_DISC: // Master Disconnect { LogError(LOG_NET, "PEER %u master disconnect, remotePeerId = %u", m_peerId, m_remotePeerId); diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 3c55f95f..9709c0fd 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -290,6 +290,11 @@ namespace network * @param callback */ void setKeyResponseCallback(std::function&& callback) { m_keyRespCallback = callback; } + /** + * @brief Helper to set the LLA encryption key response callback. + * @param callback + */ + void setLLAKeyResponseCallback(std::function&& callback) { m_llaKeyRespCallback = callback; } public: /** @@ -401,6 +406,11 @@ namespace network * (This is called once the master responds to a key request.) */ std::function m_keyRespCallback; + /** + * @brief LLA Encryption Key Response Function Callback. + * (This is called once the master responds to a key LLA request.) + */ + std::function m_llaKeyRespCallback; /** * @brief Helper to verify the given RTP sequence for the given RTP stream. diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index b1a6d4b5..54a55685 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -62,12 +62,14 @@ namespace network GRANT_REQ = 0x7AU, //!< Grant Request INCALL_CTRL = 0x7BU, //!< In-Call Control - KEY_REQ = 0x7CU, //!< Encryption Key Request - KEY_RSP = 0x7DU, //!< Encryption Key Response + KEY_REQ = 0x7CU, //!< Encryption Key TEK Request + KEY_RSP = 0x7DU, //!< Encryption Key TEK Response ACK = 0x7EU, //!< Packet Acknowledge NAK = 0x7FU, //!< Packet Negative Acknowledge + KEY_LLA_REQ = 0x80U, //!< Encryption Key LLA Request + KEY_LLA_RSP = 0x81U, //!< Encryption Key LLA Response KEYS_INVENTORY = 0x8EU, //!< Encryption Key Container Inventory KEYS_UPDATE = 0x8FU, //!< Encryption Key Container Update diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index 634550af..a483307f 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -205,6 +205,8 @@ namespace p25 const uint8_t ALGO_DES = 0x81U; /** @brief AES-256 */ const uint8_t ALGO_AES_256 = 0x84U; + /** @brief AES-128 */ + const uint8_t ALGO_AES_128 = 0x85U; /** @brief ARC4 */ const uint8_t ALGO_ARC4 = 0xAAU; /** @} */ diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index 3a74b7ca..fb60baf0 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.cpp @@ -56,6 +56,7 @@ const uint32_t FIXED_HA_UPDATE_INTERVAL = 30U; // 30s // --------------------------------------------------------------------------- std::timed_mutex TrafficNetwork::s_keyQueueMutex; +std::timed_mutex TrafficNetwork::s_llaKeyQueueMutex; // --------------------------------------------------------------------------- // Public Class Members @@ -101,6 +102,7 @@ TrafficNetwork::TrafficNetwork(HostFNE* host, const std::string& address, uint16 m_peerAffiliations(), m_ccPeerMap(), m_peerReplicaKeyQueue(), + m_peerReplicaLLAKeyQueue(), m_globalAff(nullptr), m_treeRoot(nullptr), m_treeLock(), @@ -618,6 +620,9 @@ void TrafficNetwork::clock(uint32_t ms) peer.second->setKeyResponseCallback([=](p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength) { processTEKResponse(&ki, algId, keyLength); }); + peer.second->setLLAKeyResponseCallback([=](uint32_t srcId, p25::kmm::KeyItem ki, uint8_t keyLength) { + processLLAResponse(srcId, &ki, keyLength); + }); } if (m_peers.size() > 0) { @@ -1913,6 +1918,138 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) } } } + } else { + LogError(LOG_MASTER, "PEER %u (%s) no local key or container and no replica masters to forward request to, no response, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), modifyKey->getKId()); + } + } + } + } + break; + + default: + break; + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_REPEATER_KEY, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_FUNC::KEY_LLA_REQ: // LLA Enc. Key Request + { + using namespace p25::defines; + using namespace p25::kmm; + + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + // is this peer allowed to request keys? + if (network->m_peerListLookup->getACL()) { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + break; + } else { + if (!peerEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but is not allowed, no response", peerId, connection->identWithQualifier().c_str()); + break; + } + } + } + + std::unique_ptr frame = KMMFactory::create(req->buffer + 11U); + if (frame == nullptr) { + LogWarning(LOG_MASTER, "PEER %u (%s), undecodable KMM frame from peer", peerId, connection->identWithQualifier().c_str()); + break; + } + + switch (frame->getMessageId()) { + case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: + { + 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()); + uint32_t requestingRid = modifyKey->getDstLLId(); + + LogInfoEx(LOG_MASTER, "PEER %u (%s) requested LLA enc. key, rsi = %u", peerId, connection->identWithQualifier().c_str(), + requestingRid); + + ::EKCKeyItem keyItem = network->m_cryptoLookup->findLLA(requestingRid); + if (!keyItem.isInvalid()) { + uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + uint8_t keyLength = keyItem.getKey(key); + + //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); + + // 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.setAlgId(modifyKey->getAlgId()); + modifyKeyRsp.setKId(0U); + modifyKeyRsp.setSrcLLId(WUID_FNE); + modifyKeyRsp.setDstLLId(requestingRid); + + KeysetItem ks = KeysetItem(); + ks.keysetId(1U); + ks.algId(modifyKey->getAlgId()); + ks.keyLength(keyLength); + + p25::kmm::KeyItem ki = p25::kmm::KeyItem(); + ki.keyFormat(KEY_FORMAT_TEK); + ki.kId((uint16_t)keyItem.kId()); + ki.sln((uint16_t)keyItem.sln()); + ki.setKey(key, keyLength); + + ks.push_back(ki); + modifyKeyRsp.setKeysetItem(ks); + + modifyKeyRsp.encode(buffer + 11U); + + network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_LLA_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + RTP_END_OF_CALL_SEQ, network->createStreamId()); + } else { + // attempt to forward KMM key request to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + LogInfoEx(LOG_PEER, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, rsi = %u", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), requestingRid); + + bool locked = network->s_llaKeyQueueMutex.try_lock_for(std::chrono::milliseconds(60)); + network->m_peerReplicaLLAKeyQueue[peerId] = modifyKey->getDstLLId(); + + if (locked) + network->s_llaKeyQueueMutex.unlock(); + + peer.second->writeMaster({ NET_FUNC::KEY_LLA_REQ, NET_SUBFUNC::NOP }, + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false); + } + } + } + } else { + LogError(LOG_MASTER, "PEER %u (%s) requested LLA enc. key with no local key and no upstream masters to query, algId = $%02X, rsi = %u, no response", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), requestingRid); } } } @@ -3452,3 +3589,73 @@ void TrafficNetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, s_keyQueueMutex.unlock(); } + +/* Helper to process a FNE KMM LLA response. */ + +void TrafficNetwork::processLLAResponse(uint32_t srcId, p25::kmm::KeyItem* rspKi, uint8_t keyLength) +{ + using namespace p25::defines; + using namespace p25::kmm; + + if (rspKi == nullptr) + return; + + LogInfoEx(LOG_PEER, "upstream master LLA enc. key, rsi = %u", srcId); + + s_llaKeyQueueMutex.lock(); + + std::vector peersToRemove; + for (auto entry : m_peerReplicaLLAKeyQueue) { + uint32_t requestingRid = entry.second; + if (requestingRid == srcId) { + uint32_t peerId = entry.first; + + uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + rspKi->getKey(key); + + if (m_debug) { + LogDebugEx(LOG_HOST, "TrafficNetwork::processLLAResponse()", "keyLength = %u", keyLength); + Utils::dump(1U, "TrafficNetwork::processLLAResponse(), 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.setAlgId(ALGO_AES_128); + modifyKeyRsp.setKId(0U); + modifyKeyRsp.setSrcLLId(WUID_FNE); + modifyKeyRsp.setDstLLId(srcId); + + KeysetItem ks = KeysetItem(); + ks.keysetId(1U); + ks.algId(ALGO_AES_128); + ks.keyLength(keyLength); + + p25::kmm::KeyItem ki = p25::kmm::KeyItem(); + ki.keyFormat(KEY_FORMAT_TEK); + ki.kId(rspKi->kId()); + ki.sln(rspKi->sln()); + ki.setKey(key, keyLength); + + ks.push_back(ki); + modifyKeyRsp.setKeysetItem(ks); + + modifyKeyRsp.encode(buffer + 11U); + + writePeer(peerId, m_peerId, { NET_FUNC::KEY_LLA_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + RTP_END_OF_CALL_SEQ, createStreamId()); + + peersToRemove.push_back(peerId); + } + } + + // remove peers who were sent keys + for (auto& peerId : peersToRemove) + m_peerReplicaLLAKeyQueue.erase(peerId); + + s_llaKeyQueueMutex.unlock(); +} diff --git a/src/fne/network/TrafficNetwork.h b/src/fne/network/TrafficNetwork.h index d6e29eb0..c2da1899 100644 --- a/src/fne/network/TrafficNetwork.h +++ b/src/fne/network/TrafficNetwork.h @@ -347,6 +347,8 @@ namespace network concurrent::shared_unordered_map> m_ccPeerMap; static std::timed_mutex s_keyQueueMutex; std::unordered_map m_peerReplicaKeyQueue; + static std::timed_mutex s_llaKeyQueueMutex; + std::unordered_map m_peerReplicaLLAKeyQueue; fne_lookups::AffiliationLookup* m_globalAff; @@ -836,6 +838,14 @@ namespace network * @param keyLength Length of key in bytes. */ void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); + + /** + * @brief Helper to process a FNE KMM LLA response. + * @param srcId Source Radio ID for the LLA response. + * @param ki Key Item. + * @param keyLength Length of key in bytes. + */ + void processLLAResponse(uint32_t srcId, p25::kmm::KeyItem* ki, uint8_t keyLength); }; } // namespace network diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 9972b0e8..7a412bf9 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -127,10 +127,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_ccFrameCnt(0U), m_ccSeq(0U), m_random(), - m_llaK(nullptr), - m_llaRS(nullptr), - m_llaCRS(nullptr), - m_llaKS(nullptr), + m_llaRID(), m_nid(nac), m_siteData(), m_rssiMapper(rssiMapper), @@ -173,11 +170,6 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q std::mt19937 mt(rd()); m_random = mt; - m_llaK = nullptr; - m_llaRS = new uint8_t[AUTH_KEY_LENGTH_BYTES]; - m_llaCRS = new uint8_t[AUTH_KEY_LENGTH_BYTES]; - m_llaKS = new uint8_t[AUTH_KEY_LENGTH_BYTES]; - // register RPC handlers g_RPC->registerHandler(RPC_PERMIT_P25_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this)); g_RPC->registerHandler(RPC_ACTIVE_P25_TG, RPC_FUNC_BIND(Control::RPC_activeTG, this)); @@ -205,13 +197,6 @@ Control::~Control() if (m_data != nullptr) { delete m_data; } - - if (m_llaK != nullptr) { - delete[] m_llaK; - } - delete[] m_llaRS; - delete[] m_llaCRS; - delete[] m_llaKS; } /* Resets the data states for the RF interface. */ @@ -256,34 +241,6 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_control->m_announcementGroup = (uint32_t)::strtoul(rfssConfig["announcementGroup"].as("FFFE").c_str(), NULL, 16); m_defaultNetIdleTalkgroup = (uint32_t)rfssConfig["defaultNetIdleTalkgroup"].as(0U); - yaml::Node secureConfig = rfssConfig["secure"]; - std::string key = secureConfig["key"].as(); - if (!key.empty()) { - if (key.size() == 32) { - if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) { - const char* keyPtr = key.c_str(); - m_llaK = new uint8_t[AUTH_KEY_LENGTH_BYTES]; - ::memset(m_llaK, 0x00U, AUTH_KEY_LENGTH_BYTES); - - for (uint8_t i = 0; i < AUTH_KEY_LENGTH_BYTES; i++) { - char t[4] = {keyPtr[0], keyPtr[1], 0}; - m_llaK[i] = (uint8_t)::strtoul(t, NULL, 16); - keyPtr += 2 * sizeof(char); - } - } - else { - LogWarning(LOG_P25, "Invalid characters in the secure key. LLA disabled."); - } - } - else { - LogWarning(LOG_P25, "Invalid secure key length, key should be 16 hex pairs, or 32 characters. LLA disabled."); - } - } - - if (m_llaK != nullptr) { - generateLLA_AM1_Parameters(); - } - m_ignorePDUCRC = p25Protocol["ignoreDataCRC"].as(false); m_inhibitUnauth = p25Protocol["inhibitUnauthorized"].as(false); @@ -295,8 +252,16 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_control->m_requireLLAForReg = p25Protocol["requireLLAForReg"].as(false); - if (m_llaK == nullptr) { - m_control->m_requireLLAForReg = false; + if (m_control->m_requireLLAForReg) { + if (m_network != nullptr) { + m_network->setLLAKeyResponseCallback([=](uint32_t srcId, p25::kmm::KeyItem ki, uint8_t keyLength) { + processLLAResponse(srcId, &ki, keyLength); + }); + } + + if (m_network == nullptr) { + m_control->m_requireLLAForReg = false; + } } m_control->m_noStatusAck = p25Protocol["noStatusAck"].as(false); @@ -548,10 +513,6 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw } LogInfo(" Time/Date Announcement TSBK: %s", m_control->m_ctrlTimeDateAnn ? "yes" : "no"); - if (m_llaK != nullptr) { - LogInfo(" Link Layer Authentication: yes"); - } - LogInfo(" Inhibit Unauthorized: %s", m_inhibitUnauth ? "yes" : "no"); LogInfo(" Legacy Group Grant: %s", m_legacyGroupGrnt ? "yes" : "no"); LogInfo(" Legacy Group Registration: %s", m_legacyGroupReg ? "yes" : "no"); @@ -2213,17 +2174,81 @@ void Control::RPC_touchGrantTG(json::object& req, json::object& reply) } } -/* Helper to setup and generate LLA AM1 parameters. */ +/* Helper to retrieve LLA AM1 parameters for a given source ID. */ -void Control::generateLLA_AM1_Parameters() +bool Control::getLLA_AM1_Parameters(uint32_t srcId, uint8_t* rs, uint8_t* crs, uint8_t* ks) { - ::memset(m_llaRS, 0x00U, AUTH_KEY_LENGTH_BYTES); - ::memset(m_llaCRS, 0x00U, AUTH_KEY_LENGTH_BYTES); - ::memset(m_llaKS, 0x00U, AUTH_KEY_LENGTH_BYTES); + auto it = m_llaRID.find(srcId); + if (it != m_llaRID.end()) { + LLAParams* params = it->second; + if (params != nullptr) { + if (rs != nullptr) + ::memcpy(rs, params->rs, AUTH_KEY_LENGTH_BYTES); + if (crs != nullptr) + ::memcpy(crs, params->crs, AUTH_KEY_LENGTH_BYTES); + if (ks != nullptr) + ::memcpy(ks, params->ks, AUTH_KEY_LENGTH_BYTES); + return true; + } + } else { + LogWarning(LOG_P25, "P25, LLA key not found requesting from FNE, rsi = %u", srcId); + m_network->writeLLAKeyReq(srcId); + return false; + } +} - if (m_llaK == nullptr) { - return; +/* Helper to clear LLA AM1 parameters for a given source ID. */ + +void Control::clearLLA_AM1_Parameters(uint32_t srcId) +{ + auto it = m_llaRID.find(srcId); + if (it != m_llaRID.end()) { + LLAParams* params = it->second; + if (params != nullptr) { + if (params->k != nullptr) { + delete[] params->k; + } + + delete[] params->rs; + delete[] params->crs; + delete[] params->ks; + + delete params; + } + m_llaRID.erase(it); + + if (m_verbose) { + LogInfoEx(LOG_P25, "P25, cleared LLA AM1 parameters, rsi = %u", srcId); + } } +} + +/* Helper to process a FNE KMM LLA response. */ + +void Control::processLLAResponse(uint32_t srcId, p25::kmm::KeyItem* rspKi, uint8_t keyLength) +{ + using namespace p25::kmm; + + if (rspKi == nullptr) + return; + + LogInfoEx(LOG_P25, "P25, upstream master LLA enc. key, rsi = %u", srcId); + + // initialize LLAParams structure + LLAParams* params = new LLAParams(); + params->k = new uint8_t[AUTH_KEY_LENGTH_BYTES]; + ::memset(params->k, 0x00U, AUTH_KEY_LENGTH_BYTES); + + uint8_t keyData[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + rspKi->getKey(keyData); + ::memcpy(params->k, keyData, AUTH_KEY_LENGTH_BYTES); + + params->rs = new uint8_t[AUTH_KEY_LENGTH_BYTES]; + ::memset(params->rs, 0x00U, AUTH_KEY_LENGTH_BYTES); + params->crs = new uint8_t[AUTH_KEY_LENGTH_BYTES]; + ::memset(params->crs, 0x00U, AUTH_KEY_LENGTH_BYTES); + params->ks = new uint8_t[AUTH_KEY_LENGTH_BYTES]; + ::memset(params->ks, 0x00U, AUTH_KEY_LENGTH_BYTES); crypto::AES* aes = new crypto::AES(crypto::AESKeyLength::AES_128); @@ -2241,21 +2266,42 @@ void Control::generateLLA_AM1_Parameters() // expand RS to 16 bytes for (uint32_t i = 0; i < AUTH_RAND_SEED_LENGTH_BYTES; i++) - m_llaRS[i] = RS[i]; + params->rs[i] = RS[i]; // complement RS for (uint32_t i = 0; i < AUTH_KEY_LENGTH_BYTES; i++) - m_llaCRS[i] = ~m_llaRS[i]; + params->crs[i] = ~params->rs[i]; // perform crypto - uint8_t* KS = aes->encryptECB(m_llaRS, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), m_llaK); - ::memcpy(m_llaKS, KS, AUTH_KEY_LENGTH_BYTES); + uint8_t* KS = aes->encryptECB(params->rs, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), params->k); + ::memcpy(params->ks, KS, AUTH_KEY_LENGTH_BYTES); if (m_verbose) { - LogInfoEx(LOG_P25, "P25, generated LLA AM1 parameters"); + LogInfoEx(LOG_P25, "P25, generated LLA AM1 parameters, rsi = %u", srcId); } // cleanup delete[] KS; delete aes; + + // store the parameters + auto it = m_llaRID.find(srcId); + if (it == m_llaRID.end()) { + m_llaRID[srcId] = params; + } + else { + clearLLA_AM1_Parameters(srcId); + m_llaRID[srcId] = params; + } + + // check if the source ID is currently awaiting registration, if so re-issue the auth demand which + // likely deferred earlier due to missing LLA parameters + if (std::find(m_control->m_llaDeferredAuthList.begin(), m_control->m_llaDeferredAuthList.end(), srcId) != m_control->m_llaDeferredAuthList.end()) { + if (m_verbose) { + LogInfoEx(LOG_P25, "P25, re-issuing deferred auth demand for rsi = %u", srcId); + } + + m_control->writeRF_TSDU_Auth_Dmd(srcId); + m_control->m_llaDeferredAuthList.erase(std::remove(m_control->m_llaDeferredAuthList.begin(), m_control->m_llaDeferredAuthList.end(), srcId), m_control->m_llaDeferredAuthList.end()); + } } diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 1e7a7251..811755d5 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -34,6 +34,7 @@ #include "common/network/NetRPC.h" #include "common/network/RTPFNEHeader.h" #include "common/network/Network.h" +#include "common/p25/kmm/KeysetItem.h" #include "common/p25/SiteData.h" #include "common/RingBuffer.h" #include "common/StopWatch.h" @@ -379,10 +380,16 @@ namespace p25 std::mt19937 m_random; - uint8_t* m_llaK; - uint8_t* m_llaRS; - uint8_t* m_llaCRS; - uint8_t* m_llaKS; + /** + * @brief Struct to hold LLA data for a given Radio ID for FNE KMM LLA responses. + */ + struct LLAParams { + uint8_t* k; + uint8_t* rs; + uint8_t* crs; + uint8_t* ks; + }; + std::unordered_map m_llaRID; NID m_nid; @@ -503,9 +510,27 @@ namespace p25 /** @} */ /** - * @brief Helper to setup and generate LLA AM1 parameters. + * @brief Helper to retrieve LLA AM1 parameters for a given source ID. + * @param srcId Source ID. + * @param[out] rs Buffer to store generated RS parameter. + * @param[out] crs Buffer to store generated CRS parameter. + * @param[out] ks Buffer to store generated KS parameter. + * @returns bool True, if LLA AM1 parameters were successfully retrieved, otherwise false + */ + bool getLLA_AM1_Parameters(uint32_t srcId, uint8_t* rs, uint8_t* crs, uint8_t* ks); + /** + * @brief Helper to clear LLA AM1 parameters for a given source ID. + * @param srcId Source ID. + */ + void clearLLA_AM1_Parameters(uint32_t srcId); + + /** + * @brief Helper to process a FNE KMM LLA response. + * @param srcId Source Radio ID for the LLA response. + * @param ki Key Item. + * @param keyLength Length of key in bytes. */ - void generateLLA_AM1_Parameters(); + void processLLAResponse(uint32_t srcId, p25::kmm::KeyItem* ki, uint8_t keyLength); }; } // namespace p25 diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 2da6e320..0d405095 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -597,6 +597,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrclearLLA_AM1_Parameters(srcId); } break; case TSBKO::IOSP_U_REG: @@ -643,52 +645,59 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetLLA_AM1_Parameters(srcId, nullptr, nullptr, ks)) { + crypto::AES* aes = new crypto::AES(crypto::AESKeyLength::AES_128); - // get RES1 from response - uint8_t RES1[AUTH_RES_LENGTH_BYTES]; - isp->getAuthRes(RES1); + // get RES1 from response + uint8_t RES1[AUTH_RES_LENGTH_BYTES]; + isp->getAuthRes(RES1); - // get challenge for our SU - ulong64_t challenge = 0U; - try { - challenge = m_llaDemandTable.at(srcId); - } - catch (...) { - challenge = 0U; - } + // get challenge for our SU + ulong64_t challenge = 0U; + try { + challenge = m_llaDemandTable.at(srcId); + } + catch (...) { + challenge = 0U; + } - uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES]; - SET_UINT32(challenge >> 8, RC, 0); - RC[4U] = (uint8_t)(challenge & 0xFFU); + uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES]; + SET_UINT32(challenge >> 8, RC, 0); + RC[4U] = (uint8_t)(challenge & 0xFFU); - // expand RAND1 to 16 bytes - uint8_t expandedRAND1[16]; - ::memset(expandedRAND1, 0x00U, AUTH_KEY_LENGTH_BYTES); - for (uint32_t i = 0; i < AUTH_RAND_CHLNG_LENGTH_BYTES; i++) - expandedRAND1[i] = RC[i]; + // expand RAND1 to 16 bytes + uint8_t expandedRAND1[16]; + ::memset(expandedRAND1, 0x00U, AUTH_KEY_LENGTH_BYTES); + for (uint32_t i = 0; i < AUTH_RAND_CHLNG_LENGTH_BYTES; i++) + expandedRAND1[i] = RC[i]; - // generate XRES1 - uint8_t* XRES1 = aes->encryptECB(expandedRAND1, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), m_p25->m_llaKS); + // generate XRES1 + uint8_t* XRES1 = aes->encryptECB(expandedRAND1, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), ks); - // compare RES1 and XRES1 - bool authFailed = false; - for (uint32_t i = 0; i < AUTH_RES_LENGTH_BYTES; i++) { - if (XRES1[i] != RES1[i]) { - authFailed = true; + // compare RES1 and XRES1 + bool authFailed = false; + for (uint32_t i = 0; i < AUTH_RES_LENGTH_BYTES; i++) { + if (XRES1[i] != RES1[i]) { + authFailed = true; + } } - } - // cleanup buffers - delete[] XRES1; - delete aes; + // cleanup buffers + delete[] XRES1; + delete aes; - if (!authFailed) { - writeRF_TSDU_U_Reg_Rsp(srcId, m_p25->m_siteData.sysId()); - } - else { + if (!authFailed) { + writeRF_TSDU_U_Reg_Rsp(srcId, m_p25->m_siteData.sysId()); + } + else { + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, AUTH failed, src = %u", isp->toString().c_str(), srcId); + ::ActivityLog("P25", true, "unit registration request from %u denied, authentication failure", srcId); + writeRF_TSDU_Deny(srcId, WUID_FNE, ReasonCode::DENY_SU_FAILED_AUTH, TSBKO::IOSP_U_REG); + } + } else { LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, AUTH failed, src = %u", isp->toString().c_str(), srcId); - ::ActivityLog("P25", true, "unit registration request from %u denied, authentication failure", srcId); + ::ActivityLog("P25", true, "unit registration request from %u denied, authentication failure, LLA keys not found", srcId); writeRF_TSDU_Deny(srcId, WUID_FNE, ReasonCode::DENY_SU_FAILED_AUTH, TSBKO::IOSP_U_REG); } } @@ -1279,6 +1288,7 @@ ControlSignaling::ControlSignaling(Control* p25, bool dumpTSBKData, bool debug, m_sccbTable(), m_sccbUpdateCnt(), m_llaDemandTable(), + m_llaDeferredAuthList(), m_lastMFID(MFG_STANDARD), m_noStatusAck(false), m_noMessageAck(true), @@ -3066,32 +3076,42 @@ bool ControlSignaling::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, void ControlSignaling::writeRF_TSDU_Auth_Dmd(uint32_t srcId) { - std::unique_ptr osp = std::make_unique(); - osp->setSrcId(WUID_FNE); - osp->setDstId(srcId); - osp->setAuthRS(m_p25->m_llaRS); + DECLARE_UINT8_ARRAY(rs, AUTH_KEY_LENGTH_BYTES); + if (m_p25->getLLA_AM1_Parameters(srcId, rs, nullptr, nullptr)) { + std::unique_ptr osp = std::make_unique(); + osp->setSrcId(WUID_FNE); + osp->setDstId(srcId); + osp->setAuthRS(rs); - // generate challenge - uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES]; - std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); - uint32_t rnd = dist(m_p25->m_random); - SET_UINT32(rnd, RC, 0U); + // generate challenge + uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES]; + std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); + uint32_t rnd = dist(m_p25->m_random); + SET_UINT32(rnd, RC, 0U); - rnd = dist(m_p25->m_random); - RC[4U] = (uint8_t)(rnd & 0xFFU); + rnd = dist(m_p25->m_random); + RC[4U] = (uint8_t)(rnd & 0xFFU); - ulong64_t challenge = GET_UINT32(RC, 0U); - challenge = (challenge << 8) + RC[4U]; + ulong64_t challenge = GET_UINT32(RC, 0U); + challenge = (challenge << 8) + RC[4U]; - osp->setAuthRC(RC); + osp->setAuthRC(RC); - m_llaDemandTable[srcId] = challenge; + m_llaDemandTable[srcId] = challenge; - if (m_verbose) { - LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, RC = %X", osp->toString().c_str(), srcId, challenge); - } + if (m_verbose) { + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, RC = %X", osp->toString().c_str(), srcId, challenge); + } + + writeRF_TSDU_AMBT(osp.get(), true); + } else { + if (m_verbose) { + LogInfoEx(LOG_P25, "P25, silencing subscriber and deferring auth demand, rsi = %u", srcId); + } - writeRF_TSDU_AMBT(osp.get(), true); + writeRF_TSDU_ACK_FNE(srcId, TSBKO::IOSP_U_REG, true, true); + m_llaDeferredAuthList.push_back(srcId); + } } /* Helper to write a call termination packet. */ diff --git a/src/host/p25/packet/ControlSignaling.h b/src/host/p25/packet/ControlSignaling.h index ce19d6ca..a7f19790 100644 --- a/src/host/p25/packet/ControlSignaling.h +++ b/src/host/p25/packet/ControlSignaling.h @@ -182,6 +182,7 @@ namespace p25 std::unordered_map m_sccbUpdateCnt; std::unordered_map m_llaDemandTable; + std::vector m_llaDeferredAuthList; uint8_t m_lastMFID;