implement support to send the KMM changeover;

pull/133/head
Bryan Biedenkapp 1 month ago
parent 87b347ba02
commit 5d32e6fe70

@ -82,6 +82,9 @@ void KMMChangeover::encode(uint8_t* data)
std::string KMMChangeover::toString()
{
if (m_messageId == KMM_MessageType::CHANGEOVER_RSP)
return std::string("KMM, CHANGEOVER_RSP (Changeover Response)");
return std::string("KMM, CHANGEOVER_CMD (Changeover Command)");
}

@ -135,6 +135,12 @@ std::unique_ptr<KMMFrame> KMMFactory::create(const uint8_t* data, uint32_t len)
const uint32_t available = bodyEnd - body;
switch (data[0U]) {
case KMM_MessageType::CHANGEOVER_CMD:
case KMM_MessageType::CHANGEOVER_RSP:
if (available < 1U || data[body] == 0U ||
available != 1U + (uint32_t)data[body] * 2U)
return nullptr;
break;
case KMM_MessageType::HELLO:
case KMM_MessageType::INVENTORY_CMD:
case KMM_MessageType::DEREG_RSP:

@ -58,6 +58,8 @@ using namespace p25::kmm;
bool isSupportedKMM(uint8_t messageId)
{
switch (messageId) {
case KMM_MessageType::CHANGEOVER_CMD:
case KMM_MessageType::CHANGEOVER_RSP:
case KMM_MessageType::HELLO:
case KMM_MessageType::INVENTORY_CMD:
case KMM_MessageType::INVENTORY_RSP:
@ -1264,6 +1266,73 @@ UInt8Array P25OTARService::write_KMM_Rekey_Command(uint32_t llId, uint32_t kmmRS
return firstFrame;
}
/* Helper used to build a Changeover-Command KMM for an SU. */
UInt8Array P25OTARService::write_KMM_Changeover_Command(uint32_t llId, uint32_t kmmRSI,
uint8_t supersededKeysetId, uint8_t activeKeysetId, uint32_t* payloadSize)
{
if (payloadSize != nullptr)
*payloadSize = 0U;
lookups::RadioId ridEntry = m_network->m_ridLookup->find(kmmRSI);
if (ridEntry.radioDefault() || !ridEntry.radioEnabled() || !ridEntry.canRekey()) {
LogWarning(LOG_P25, P25_KMM_STR ", cannot build Changeover Command; RID %u has no eligible key policy", kmmRSI);
return nullptr;
}
const std::vector<uint16_t> allowedKIds = ridEntry.allowedKIds();
EKCKeyItem macTekItem;
for (const EKCKeyItem& candidate : m_network->m_cryptoLookup->keys()) {
if (candidate.algId() == ALGO_AES_256 &&
(allowedKIds.empty() || std::find(allowedKIds.begin(), allowedKIds.end(),
(uint16_t)candidate.kId()) != allowedKIds.end())) {
macTekItem = candidate;
break;
}
}
if (macTekItem.isInvalid()) {
LogError(LOG_P25, P25_KMM_STR ", cannot build Changeover Command; no authorized AES TEK for RSI = %u", kmmRSI);
return nullptr;
}
uint16_t mn = 1U;
if (m_rsiMessageNumber.find(llId) != m_rsiMessageNumber.end())
mn = m_rsiMessageNumber[llId];
KMMChangeover outKmm;
outKmm.setSrcLLId(WUID_FNE);
outKmm.setDstLLId(kmmRSI);
outKmm.setSupersededKeysetId(supersededKeysetId);
outKmm.setActiveKeysetId(activeKeysetId);
outKmm.setHasMessageNumber(true);
outKmm.setMessageNumber(mn);
outKmm.setMACType(KMM_MAC::ENH_MAC);
outKmm.setMACAlgId(macTekItem.algId());
outKmm.setMACKId((uint16_t)macTekItem.kId());
outKmm.setMACFormat(KMM_MAC_FORMAT_CBC);
const uint32_t frameLength = outKmm.fullLength();
UInt8Array encoded = std::make_unique<uint8_t[]>(frameLength);
::memset(encoded.get(), 0x00U, frameLength);
outKmm.encode(encoded.get());
uint8_t macTek[P25DEF::MAX_ENC_KEY_LENGTH_BYTES] = { 0U };
macTekItem.getKey(macTek);
outKmm.generateMAC(macTek, encoded.get());
if (payloadSize != nullptr)
*payloadSize = frameLength;
m_rsiMessageNumber[llId] = (uint16_t)(mn + 1U);
if (m_verbose) {
LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, RSI = %u, supersededKeysetId = $%02X, activeKeysetId = $%02X",
outKmm.toString().c_str(), llId, kmmRSI, supersededKeysetId, activeKeysetId);
}
return encoded;
}
/* Helper used to return a Registration-Command KMM to the calling SU. */
UInt8Array P25OTARService::write_KMM_Reg_Command(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize)

@ -197,6 +197,18 @@ namespace network
UInt8Array write_KMM_Rekey_Command(uint32_t llId, uint32_t kmmRSI, uint8_t flags, uint32_t* payloadSize,
const KMMAuthContext& auth, std::vector<std::vector<uint8_t>>* additionalResponses = nullptr);
/**
* @brief Builds an authenticated Changeover-Command KMM for an SU.
* @param llId Logical Link Address used to track the outbound message number.
* @param kmmRSI Destination KMM Radio Set Identifier.
* @param supersededKeysetId Keyset ID being superseded.
* @param activeKeysetId Keyset ID becoming active.
* @param[out] payloadSize Size of the returned KMM payload.
* @returns Encoded KMM Changeover Command, or nullptr if no authorized MAC TEK is available.
*/
UInt8Array write_KMM_Changeover_Command(uint32_t llId, uint32_t kmmRSI,
uint8_t supersededKeysetId, uint8_t activeKeysetId, uint32_t* payloadSize);
/**
* @brief Helper used to return a Registration-Command KMM to the calling SU.
* @param llId Logical Link Address.

@ -125,6 +125,23 @@ std::vector<std::vector<uint8_t>> FNETestHooks::buildOTARRekey(TrafficNetwork& n
return frames;
}
/** Builds an OTAR Changeover Command without dispatching it. */
std::vector<uint8_t> FNETestHooks::buildOTARChangeover(TrafficNetwork& network,
uint32_t llId, uint32_t kmmRSI, uint8_t supersededKeysetId, uint8_t activeKeysetId)
{
if (network.m_p25OTARService == nullptr)
return {};
uint32_t length = 0U;
UInt8Array encoded = network.m_p25OTARService->write_KMM_Changeover_Command(llId,
kmmRSI, supersededKeysetId, activeKeysetId, &length);
if (encoded == nullptr || length == 0U)
return {};
return std::vector<uint8_t>(encoded.get(), encoded.get() + length);
}
/**
* @brief Resolves the security parameters of an OTAR response packet.
* @param network The TrafficNetwork instance.

@ -107,6 +107,17 @@ public:
*/
static std::vector<std::vector<uint8_t>> buildOTARRekey(network::TrafficNetwork& network,
uint32_t llId, uint32_t kmmRSI);
/**
* @brief Builds an OTAR Changeover Command without dispatching it.
* @param network The TrafficNetwork instance.
* @param llId Logical Link ID used for message-number tracking.
* @param kmmRSI Destination KMM RSI.
* @param supersededKeysetId Keyset ID being superseded.
* @param activeKeysetId Keyset ID becoming active.
* @return Encoded command, or an empty vector when it cannot be built.
*/
static std::vector<uint8_t> buildOTARChangeover(network::TrafficNetwork& network,
uint32_t llId, uint32_t kmmRSI, uint8_t supersededKeysetId, uint8_t activeKeysetId);
/**
* @brief Resolves the outer security context selected for a generated response.
* @param network The TrafficNetwork instance.

@ -10,6 +10,7 @@
#include "host/Defines.h"
#include "common/p25/P25Defines.h"
#include "common/p25/kmm/KMMFactory.h"
#include "common/p25/kmm/KMMChangeover.h"
#include "common/p25/kmm/KMMDeregistrationCommand.h"
#include "common/p25/kmm/KMMDeregistrationResponse.h"
#include "common/p25/kmm/KMMHello.h"
@ -620,6 +621,58 @@ TEST_CASE("P25 OTAR Rekey Commands batch four keys per KMM", "[p25][kmm][otar][r
}
}
TEST_CASE("P25 OTAR builds authenticated Changeover Commands without dispatching them",
"[p25][kmm][otar][changeover]")
{
KMMFNEHarness harness;
constexpr uint32_t SU_RSI = 0x654321U;
constexpr uint16_t TEK_KID = 0x2345U;
constexpr uint8_t SUPERSEDED_KEYSET = 0x01U;
constexpr uint8_t ACTIVE_KEYSET = 0x02U;
EKCKeyItem tek;
tek.id(1U);
tek.algId(ALGO_AES_256);
tek.kId(TEK_KID);
tek.sln(1U);
tek.keyMaterial("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
FNETestHooks::addCryptoKey(harness.traffic, tek);
harness.rid.addEntry(SU_RSI, true, "changeover-test", "", true, true, { TEK_KID });
const std::vector<uint8_t> encoded = FNETestHooks::buildOTARChangeover(harness.traffic,
SU_RSI, SU_RSI, SUPERSEDED_KEYSET, ACTIVE_KEYSET);
REQUIRE_FALSE(encoded.empty());
std::unique_ptr<KMMFrame> base = KMMFactory::create(encoded.data(), (uint32_t)encoded.size());
REQUIRE(base != nullptr);
KMMChangeover* changeover = dynamic_cast<KMMChangeover*>(base.get());
REQUIRE(changeover != nullptr);
CHECK(changeover->getMessageId() == KMM_MessageType::CHANGEOVER_CMD);
CHECK(changeover->getResponseKind() == KMM_ResponseKind::IMMEDIATE);
CHECK(changeover->getSrcLLId() == WUID_FNE);
CHECK(changeover->getDstLLId() == SU_RSI);
CHECK(changeover->getSupersededKeysetId() == SUPERSEDED_KEYSET);
CHECK(changeover->getActiveKeysetId() == ACTIVE_KEYSET);
CHECK(changeover->getHasMessageNumber());
CHECK(changeover->getMessageNumber() == 1U);
CHECK(changeover->getMACType() == KMM_MAC::ENH_MAC);
CHECK(changeover->getMACAlgId() == ALGO_AES_256);
CHECK(changeover->getMACKId() == TEK_KID);
uint8_t material[P25DEF::MAX_ENC_KEY_LENGTH_BYTES] = { 0U };
tek.getKey(material);
CHECK(changeover->verifyMAC(material, encoded.data(), (uint32_t)encoded.size()));
bool encrypted = false;
uint8_t algoId = ALGO_UNENCRYPT;
uint16_t kid = 0U;
REQUIRE(FNETestHooks::resolveOTARResponseSecurity(harness.traffic, encoded,
encrypted, algoId, kid));
CHECK(encrypted);
CHECK(algoId == ALGO_AES_256);
CHECK(kid == TEK_KID);
}
#if !defined(_WIN32)
TEST_CASE("P25 OTAR DLI traverses the real UDP endpoint", "[p25][kmm][otar][dli][integration]")
{

Loading…
Cancel
Save

Powered by TurnKey Linux.