now that we have proper UKEK and LLA FNE-side in the crypto container, implement proper per RID LLA support;

pull/121/merge
Bryan Biedenkapp 1 month ago
parent 7885ecc151
commit da4669659a

@ -526,12 +526,6 @@ system:
# RPC access password for voice channel. # RPC access password for voice channel.
rpcPassword: "ULTRA-VERY-SECURE-DEFAULT" 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. # DMR Color Code.
colorCode: 1 colorCode: 1
# P25 Network Access Code (NAC). (Rx/Tx) # P25 Network Access Code (NAC). (Rx/Tx)

@ -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); 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. */ /* Writes the local activity log to the network. */
bool BaseNetwork::writeActLog(const char* message) bool BaseNetwork::writeActLog(const char* message)

@ -466,6 +466,13 @@ namespace network
*/ */
bool writeKeyReq(const uint16_t kId, const uint8_t algId, const uint32_t srcId = 0U); 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. * @brief Writes the local activity log to the network.
* \code{.unparsed} * \code{.unparsed}

@ -82,7 +82,8 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort,
m_p25InCallCallback(nullptr), m_p25InCallCallback(nullptr),
m_nxdnInCallCallback(nullptr), m_nxdnInCallCallback(nullptr),
m_analogInCallCallback(nullptr), m_analogInCallCallback(nullptr),
m_keyRespCallback(nullptr) m_keyRespCallback(nullptr),
m_llaKeyRespCallback(nullptr)
{ {
assert(!address.empty()); assert(!address.empty());
assert(port > 0U); assert(port > 0U);
@ -1231,6 +1232,47 @@ void Network::clock(uint32_t ms)
} }
} }
break; break;
case NET_FUNC::KEY_LLA_RSP: // LLA Enc. Key Response
{
if (m_enabled) {
using namespace p25::kmm;
std::unique_ptr<KMMFrame> 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<KMMModifyKey*>(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 case NET_FUNC::MST_DISC: // Master Disconnect
{ {
LogError(LOG_NET, "PEER %u master disconnect, remotePeerId = %u", m_peerId, m_remotePeerId); LogError(LOG_NET, "PEER %u master disconnect, remotePeerId = %u", m_peerId, m_remotePeerId);

@ -290,6 +290,11 @@ namespace network
* @param callback * @param callback
*/ */
void setKeyResponseCallback(std::function<void(p25::kmm::KeyItem, uint8_t, uint8_t)>&& callback) { m_keyRespCallback = callback; } void setKeyResponseCallback(std::function<void(p25::kmm::KeyItem, uint8_t, uint8_t)>&& callback) { m_keyRespCallback = callback; }
/**
* @brief Helper to set the LLA encryption key response callback.
* @param callback
*/
void setLLAKeyResponseCallback(std::function<void(uint32_t, p25::kmm::KeyItem, uint8_t)>&& callback) { m_llaKeyRespCallback = callback; }
public: public:
/** /**
@ -401,6 +406,11 @@ namespace network
* (This is called once the master responds to a key request.) * (This is called once the master responds to a key request.)
*/ */
std::function<void(p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength)> m_keyRespCallback; std::function<void(p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength)> m_keyRespCallback;
/**
* @brief LLA Encryption Key Response Function Callback.
* (This is called once the master responds to a key LLA request.)
*/
std::function<void(uint32_t rsi, p25::kmm::KeyItem ki, uint8_t keyLength)> m_llaKeyRespCallback;
/** /**
* @brief Helper to verify the given RTP sequence for the given RTP stream. * @brief Helper to verify the given RTP sequence for the given RTP stream.

@ -62,12 +62,14 @@ namespace network
GRANT_REQ = 0x7AU, //!< Grant Request GRANT_REQ = 0x7AU, //!< Grant Request
INCALL_CTRL = 0x7BU, //!< In-Call Control INCALL_CTRL = 0x7BU, //!< In-Call Control
KEY_REQ = 0x7CU, //!< Encryption Key Request KEY_REQ = 0x7CU, //!< Encryption Key TEK Request
KEY_RSP = 0x7DU, //!< Encryption Key Response KEY_RSP = 0x7DU, //!< Encryption Key TEK Response
ACK = 0x7EU, //!< Packet Acknowledge ACK = 0x7EU, //!< Packet Acknowledge
NAK = 0x7FU, //!< Packet Negative 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_INVENTORY = 0x8EU, //!< Encryption Key Container Inventory
KEYS_UPDATE = 0x8FU, //!< Encryption Key Container Update KEYS_UPDATE = 0x8FU, //!< Encryption Key Container Update

@ -205,6 +205,8 @@ namespace p25
const uint8_t ALGO_DES = 0x81U; const uint8_t ALGO_DES = 0x81U;
/** @brief AES-256 */ /** @brief AES-256 */
const uint8_t ALGO_AES_256 = 0x84U; const uint8_t ALGO_AES_256 = 0x84U;
/** @brief AES-128 */
const uint8_t ALGO_AES_128 = 0x85U;
/** @brief ARC4 */ /** @brief ARC4 */
const uint8_t ALGO_ARC4 = 0xAAU; const uint8_t ALGO_ARC4 = 0xAAU;
/** @} */ /** @} */

@ -56,6 +56,7 @@ const uint32_t FIXED_HA_UPDATE_INTERVAL = 30U; // 30s
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
std::timed_mutex TrafficNetwork::s_keyQueueMutex; std::timed_mutex TrafficNetwork::s_keyQueueMutex;
std::timed_mutex TrafficNetwork::s_llaKeyQueueMutex;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public Class Members // Public Class Members
@ -101,6 +102,7 @@ TrafficNetwork::TrafficNetwork(HostFNE* host, const std::string& address, uint16
m_peerAffiliations(), m_peerAffiliations(),
m_ccPeerMap(), m_ccPeerMap(),
m_peerReplicaKeyQueue(), m_peerReplicaKeyQueue(),
m_peerReplicaLLAKeyQueue(),
m_globalAff(nullptr), m_globalAff(nullptr),
m_treeRoot(nullptr), m_treeRoot(nullptr),
m_treeLock(), 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) { peer.second->setKeyResponseCallback([=](p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength) {
processTEKResponse(&ki, algId, 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) { 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<KMMFrame> 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<KMMModifyKey*>(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(); 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<uint32_t> 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();
}

@ -347,6 +347,8 @@ namespace network
concurrent::shared_unordered_map<uint32_t, std::vector<uint32_t>> m_ccPeerMap; concurrent::shared_unordered_map<uint32_t, std::vector<uint32_t>> m_ccPeerMap;
static std::timed_mutex s_keyQueueMutex; static std::timed_mutex s_keyQueueMutex;
std::unordered_map<uint32_t, uint16_t> m_peerReplicaKeyQueue; std::unordered_map<uint32_t, uint16_t> m_peerReplicaKeyQueue;
static std::timed_mutex s_llaKeyQueueMutex;
std::unordered_map<uint32_t, uint16_t> m_peerReplicaLLAKeyQueue;
fne_lookups::AffiliationLookup* m_globalAff; fne_lookups::AffiliationLookup* m_globalAff;
@ -836,6 +838,14 @@ namespace network
* @param keyLength Length of key in bytes. * @param keyLength Length of key in bytes.
*/ */
void processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t keyLength); 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 } // namespace network

@ -127,10 +127,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
m_ccFrameCnt(0U), m_ccFrameCnt(0U),
m_ccSeq(0U), m_ccSeq(0U),
m_random(), m_random(),
m_llaK(nullptr), m_llaRID(),
m_llaRS(nullptr),
m_llaCRS(nullptr),
m_llaKS(nullptr),
m_nid(nac), m_nid(nac),
m_siteData(), m_siteData(),
m_rssiMapper(rssiMapper), m_rssiMapper(rssiMapper),
@ -173,11 +170,6 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
std::mt19937 mt(rd()); std::mt19937 mt(rd());
m_random = mt; 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 // register RPC handlers
g_RPC->registerHandler(RPC_PERMIT_P25_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this)); 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)); g_RPC->registerHandler(RPC_ACTIVE_P25_TG, RPC_FUNC_BIND(Control::RPC_activeTG, this));
@ -205,13 +197,6 @@ Control::~Control()
if (m_data != nullptr) { if (m_data != nullptr) {
delete m_data; 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. */ /* 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<std::string>("FFFE").c_str(), NULL, 16); m_control->m_announcementGroup = (uint32_t)::strtoul(rfssConfig["announcementGroup"].as<std::string>("FFFE").c_str(), NULL, 16);
m_defaultNetIdleTalkgroup = (uint32_t)rfssConfig["defaultNetIdleTalkgroup"].as<uint32_t>(0U); m_defaultNetIdleTalkgroup = (uint32_t)rfssConfig["defaultNetIdleTalkgroup"].as<uint32_t>(0U);
yaml::Node secureConfig = rfssConfig["secure"];
std::string key = secureConfig["key"].as<std::string>();
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<bool>(false); m_ignorePDUCRC = p25Protocol["ignoreDataCRC"].as<bool>(false);
m_inhibitUnauth = p25Protocol["inhibitUnauthorized"].as<bool>(false); m_inhibitUnauth = p25Protocol["inhibitUnauthorized"].as<bool>(false);
@ -295,8 +252,16 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
m_control->m_requireLLAForReg = p25Protocol["requireLLAForReg"].as<bool>(false); m_control->m_requireLLAForReg = p25Protocol["requireLLAForReg"].as<bool>(false);
if (m_llaK == nullptr) { if (m_control->m_requireLLAForReg) {
m_control->m_requireLLAForReg = false; 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<bool>(false); m_control->m_noStatusAck = p25Protocol["noStatusAck"].as<bool>(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"); 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(" Inhibit Unauthorized: %s", m_inhibitUnauth ? "yes" : "no");
LogInfo(" Legacy Group Grant: %s", m_legacyGroupGrnt ? "yes" : "no"); LogInfo(" Legacy Group Grant: %s", m_legacyGroupGrnt ? "yes" : "no");
LogInfo(" Legacy Group Registration: %s", m_legacyGroupReg ? "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); auto it = m_llaRID.find(srcId);
::memset(m_llaCRS, 0x00U, AUTH_KEY_LENGTH_BYTES); if (it != m_llaRID.end()) {
::memset(m_llaKS, 0x00U, AUTH_KEY_LENGTH_BYTES); 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) { /* Helper to clear LLA AM1 parameters for a given source ID. */
return;
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); crypto::AES* aes = new crypto::AES(crypto::AESKeyLength::AES_128);
@ -2241,21 +2266,42 @@ void Control::generateLLA_AM1_Parameters()
// expand RS to 16 bytes // expand RS to 16 bytes
for (uint32_t i = 0; i < AUTH_RAND_SEED_LENGTH_BYTES; i++) for (uint32_t i = 0; i < AUTH_RAND_SEED_LENGTH_BYTES; i++)
m_llaRS[i] = RS[i]; params->rs[i] = RS[i];
// complement RS // complement RS
for (uint32_t i = 0; i < AUTH_KEY_LENGTH_BYTES; i++) 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 // perform crypto
uint8_t* KS = aes->encryptECB(m_llaRS, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), m_llaK); uint8_t* KS = aes->encryptECB(params->rs, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), params->k);
::memcpy(m_llaKS, KS, AUTH_KEY_LENGTH_BYTES); ::memcpy(params->ks, KS, AUTH_KEY_LENGTH_BYTES);
if (m_verbose) { if (m_verbose) {
LogInfoEx(LOG_P25, "P25, generated LLA AM1 parameters"); LogInfoEx(LOG_P25, "P25, generated LLA AM1 parameters, rsi = %u", srcId);
} }
// cleanup // cleanup
delete[] KS; delete[] KS;
delete aes; 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());
}
} }

@ -34,6 +34,7 @@
#include "common/network/NetRPC.h" #include "common/network/NetRPC.h"
#include "common/network/RTPFNEHeader.h" #include "common/network/RTPFNEHeader.h"
#include "common/network/Network.h" #include "common/network/Network.h"
#include "common/p25/kmm/KeysetItem.h"
#include "common/p25/SiteData.h" #include "common/p25/SiteData.h"
#include "common/RingBuffer.h" #include "common/RingBuffer.h"
#include "common/StopWatch.h" #include "common/StopWatch.h"
@ -379,10 +380,16 @@ namespace p25
std::mt19937 m_random; std::mt19937 m_random;
uint8_t* m_llaK; /**
uint8_t* m_llaRS; * @brief Struct to hold LLA data for a given Radio ID for FNE KMM LLA responses.
uint8_t* m_llaCRS; */
uint8_t* m_llaKS; struct LLAParams {
uint8_t* k;
uint8_t* rs;
uint8_t* crs;
uint8_t* ks;
};
std::unordered_map<uint32_t, LLAParams*> m_llaRID;
NID m_nid; 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 } // namespace p25

@ -597,6 +597,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
} }
writeRF_TSDU_U_Dereg_Ack(srcId); writeRF_TSDU_U_Dereg_Ack(srcId);
if (m_requireLLAForReg)
m_p25->clearLLA_AM1_Parameters(srcId);
} }
break; break;
case TSBKO::IOSP_U_REG: case TSBKO::IOSP_U_REG:
@ -643,52 +645,59 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
::ActivityLog("P25", true, "authentication response from %u", srcId); ::ActivityLog("P25", true, "authentication response from %u", srcId);
crypto::AES* aes = new crypto::AES(crypto::AESKeyLength::AES_128); DECLARE_UINT8_ARRAY(ks, AUTH_KEY_LENGTH_BYTES);
if (m_p25->getLLA_AM1_Parameters(srcId, nullptr, nullptr, ks)) {
crypto::AES* aes = new crypto::AES(crypto::AESKeyLength::AES_128);
// get RES1 from response // get RES1 from response
uint8_t RES1[AUTH_RES_LENGTH_BYTES]; uint8_t RES1[AUTH_RES_LENGTH_BYTES];
isp->getAuthRes(RES1); isp->getAuthRes(RES1);
// get challenge for our SU // get challenge for our SU
ulong64_t challenge = 0U; ulong64_t challenge = 0U;
try { try {
challenge = m_llaDemandTable.at(srcId); challenge = m_llaDemandTable.at(srcId);
} }
catch (...) { catch (...) {
challenge = 0U; challenge = 0U;
} }
uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES]; uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES];
SET_UINT32(challenge >> 8, RC, 0); SET_UINT32(challenge >> 8, RC, 0);
RC[4U] = (uint8_t)(challenge & 0xFFU); RC[4U] = (uint8_t)(challenge & 0xFFU);
// expand RAND1 to 16 bytes // expand RAND1 to 16 bytes
uint8_t expandedRAND1[16]; uint8_t expandedRAND1[16];
::memset(expandedRAND1, 0x00U, AUTH_KEY_LENGTH_BYTES); ::memset(expandedRAND1, 0x00U, AUTH_KEY_LENGTH_BYTES);
for (uint32_t i = 0; i < AUTH_RAND_CHLNG_LENGTH_BYTES; i++) for (uint32_t i = 0; i < AUTH_RAND_CHLNG_LENGTH_BYTES; i++)
expandedRAND1[i] = RC[i]; expandedRAND1[i] = RC[i];
// generate XRES1 // generate XRES1
uint8_t* XRES1 = aes->encryptECB(expandedRAND1, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), m_p25->m_llaKS); uint8_t* XRES1 = aes->encryptECB(expandedRAND1, AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), ks);
// compare RES1 and XRES1 // compare RES1 and XRES1
bool authFailed = false; bool authFailed = false;
for (uint32_t i = 0; i < AUTH_RES_LENGTH_BYTES; i++) { for (uint32_t i = 0; i < AUTH_RES_LENGTH_BYTES; i++) {
if (XRES1[i] != RES1[i]) { if (XRES1[i] != RES1[i]) {
authFailed = true; authFailed = true;
}
} }
}
// cleanup buffers // cleanup buffers
delete[] XRES1; delete[] XRES1;
delete aes; delete aes;
if (!authFailed) { if (!authFailed) {
writeRF_TSDU_U_Reg_Rsp(srcId, m_p25->m_siteData.sysId()); writeRF_TSDU_U_Reg_Rsp(srcId, m_p25->m_siteData.sysId());
} }
else { 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); 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); 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_sccbTable(),
m_sccbUpdateCnt(), m_sccbUpdateCnt(),
m_llaDemandTable(), m_llaDemandTable(),
m_llaDeferredAuthList(),
m_lastMFID(MFG_STANDARD), m_lastMFID(MFG_STANDARD),
m_noStatusAck(false), m_noStatusAck(false),
m_noMessageAck(true), 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) void ControlSignaling::writeRF_TSDU_Auth_Dmd(uint32_t srcId)
{ {
std::unique_ptr<MBT_OSP_AUTH_DMD> osp = std::make_unique<MBT_OSP_AUTH_DMD>(); DECLARE_UINT8_ARRAY(rs, AUTH_KEY_LENGTH_BYTES);
osp->setSrcId(WUID_FNE); if (m_p25->getLLA_AM1_Parameters(srcId, rs, nullptr, nullptr)) {
osp->setDstId(srcId); std::unique_ptr<MBT_OSP_AUTH_DMD> osp = std::make_unique<MBT_OSP_AUTH_DMD>();
osp->setAuthRS(m_p25->m_llaRS); osp->setSrcId(WUID_FNE);
osp->setDstId(srcId);
osp->setAuthRS(rs);
// generate challenge // generate challenge
uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES]; uint8_t RC[AUTH_RAND_CHLNG_LENGTH_BYTES];
std::uniform_int_distribution<uint32_t> dist(DVM_RAND_MIN, DVM_RAND_MAX); std::uniform_int_distribution<uint32_t> dist(DVM_RAND_MIN, DVM_RAND_MAX);
uint32_t rnd = dist(m_p25->m_random); uint32_t rnd = dist(m_p25->m_random);
SET_UINT32(rnd, RC, 0U); SET_UINT32(rnd, RC, 0U);
rnd = dist(m_p25->m_random); rnd = dist(m_p25->m_random);
RC[4U] = (uint8_t)(rnd & 0xFFU); RC[4U] = (uint8_t)(rnd & 0xFFU);
ulong64_t challenge = GET_UINT32(RC, 0U); ulong64_t challenge = GET_UINT32(RC, 0U);
challenge = (challenge << 8) + RC[4U]; challenge = (challenge << 8) + RC[4U];
osp->setAuthRC(RC); osp->setAuthRC(RC);
m_llaDemandTable[srcId] = challenge; m_llaDemandTable[srcId] = challenge;
if (m_verbose) { if (m_verbose) {
LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, RC = %X", osp->toString().c_str(), srcId, challenge); 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. */ /* Helper to write a call termination packet. */

@ -182,6 +182,7 @@ namespace p25
std::unordered_map<uint8_t, uint8_t> m_sccbUpdateCnt; std::unordered_map<uint8_t, uint8_t> m_sccbUpdateCnt;
std::unordered_map<uint32_t, ulong64_t> m_llaDemandTable; std::unordered_map<uint32_t, ulong64_t> m_llaDemandTable;
std::vector<uint32_t> m_llaDeferredAuthList;
uint8_t m_lastMFID; uint8_t m_lastMFID;

Loading…
Cancel
Save

Powered by TurnKey Linux.