From 936402275fd2e1fe4d50b93faf278a59200c53de Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 1 Dec 2023 17:37:47 -0500 Subject: [PATCH] very very preliminary work for U_REG LLA support; --- configs/config.example.yml | 8 +++ src/p25/Control.cpp | 100 ++++++++++++++++++++++++++ src/p25/Control.h | 11 +++ src/p25/P25Defines.h | 2 + src/p25/packet/ControlSignaling.cpp | 106 +++++++++++++++++++++++++++- src/p25/packet/ControlSignaling.h | 8 ++- 6 files changed, 233 insertions(+), 2 deletions(-) diff --git a/configs/config.example.yml b/configs/config.example.yml index e7ca1b2a..c8e1961f 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -206,6 +206,8 @@ protocols: verifyAff: false # Flag indicating the host should verify unit registration. verifyReg: false + # Flag indicating the host requires LLA verification before allowing unit registration. + requireLLAForReg: false # Flag indicating whether verbose dumping of P25 data packets is enabled. dumpDataPacket: false # Flag indicating whether or not this host will repeat P25 data traffic. @@ -376,6 +378,12 @@ system: # REST API access password for voice channel. restPassword: "PASSWORD" + 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/p25/Control.cpp b/src/p25/Control.cpp index ba092573..f7484161 100644 --- a/src/p25/Control.cpp +++ b/src/p25/Control.cpp @@ -36,6 +36,7 @@ #include "p25/Sync.h" #include "edac/CRC.h" #include "remote/RESTClient.h" +#include "AESCrypto.h" #include "HostMain.h" #include "Log.h" #include "Utils.h" @@ -137,6 +138,11 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_frameLossThreshold(DEFAULT_FRAME_LOSS_THRESHOLD), m_ccFrameCnt(0U), m_ccSeq(0U), + m_random(), + m_llaK(nullptr), + m_llaRS(nullptr), + m_llaCRS(nullptr), + m_llaKS(nullptr), m_nid(nac), m_siteData(), m_rssiMapper(rssiMapper), @@ -161,6 +167,15 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_voice = new Voice(this, debug, verbose); m_control = new ControlSignaling(this, dumpTSBKData, debug, verbose); m_data = new Data(this, dumpPDUData, repeatPDU, debug, verbose); + + std::random_device rd; + std::mt19937 mt(rd()); + m_random = mt; + + m_llaK = nullptr; + m_llaRS = new uint8_t[P25_AUTH_KEY_LENGTH_BYTES]; + m_llaCRS = new uint8_t[P25_AUTH_KEY_LENGTH_BYTES]; + m_llaKS = new uint8_t[P25_AUTH_KEY_LENGTH_BYTES]; } /// @@ -233,6 +248,34 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_control->m_patchSuperGroup = (uint32_t)::strtoul(rfssConfig["pSuperGroup"].as("FFFE").c_str(), NULL, 16); m_control->m_announcementGroup = (uint32_t)::strtoul(rfssConfig["announcementGroup"].as("FFFE").c_str(), NULL, 16); + 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[P25_AUTH_KEY_LENGTH_BYTES]; + ::memset(m_llaK, 0x00U, P25_AUTH_KEY_LENGTH_BYTES); + + for (uint8_t i = 0; i < P25_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_inhibitUnauth = p25Protocol["inhibitUnauthorized"].as(false); m_legacyGroupGrnt = p25Protocol["legacyGroupGrnt"].as(true); m_legacyGroupReg = p25Protocol["legacyGroupReg"].as(false); @@ -240,6 +283,12 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_control->m_verifyAff = p25Protocol["verifyAff"].as(false); m_control->m_verifyReg = p25Protocol["verifyReg"].as(false); + m_control->m_requireLLAForReg = p25Protocol["requireLLAForReg"].as(false); + + if (m_llaK == nullptr) { + m_control->m_requireLLAForReg = false; + } + m_control->m_noStatusAck = p25Protocol["noStatusAck"].as(false); m_control->m_noMessageAck = p25Protocol["noMessageAck"].as(true); m_control->m_unitToUnitAvailCheck = p25Protocol["unitToUnitAvailCheck"].as(true); @@ -433,11 +482,16 @@ 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"); LogInfo(" Verify Affiliation: %s", m_control->m_verifyAff ? "yes" : "no"); LogInfo(" Verify Registration: %s", m_control->m_verifyReg ? "yes" : "no"); + LogInfo(" Require LLA for Registration: %s", m_control->m_requireLLAForReg ? "yes" : "no"); LogInfo(" SNDCP Channel Grant: %s", m_control->m_sndcpChGrant ? "yes" : "no"); @@ -1582,3 +1636,49 @@ void Control::writeRF_TDU(bool noNetwork) addFrame(data, P25_TDU_FRAME_LENGTH_BYTES + 2U); } } + +/// +/// Helper to setup and generate LLA AM1 parameters. +/// +void Control::generateLLA_AM1_Parameters() +{ + ::memset(m_llaRS, 0x00U, P25_AUTH_KEY_LENGTH_BYTES); + ::memset(m_llaCRS, 0x00U, P25_AUTH_KEY_LENGTH_BYTES); + ::memset(m_llaKS, 0x00U, P25_AUTH_KEY_LENGTH_BYTES); + + if (m_llaK == nullptr) { + return; + } + + crypto::AES* aes = new crypto::AES(crypto::AESKeyLength::AES_128); + + // generate new RS + uint8_t RS[P25_AUTH_RAND_SEED_LENGTH_BYTES]; + std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); + uint32_t rnd = dist(m_random); + __SET_UINT32(rnd, RS, 0U); + + rnd = dist(m_random); + __SET_UINT32(rnd, RS, 4U); + + rnd = dist(m_random); + RS[9U] = (uint8_t)(rnd & 0xFFU); + + // expand RS to 16 bytes + for (uint32_t i = 0; i < P25_AUTH_RAND_SEED_LENGTH_BYTES; i++) + m_llaRS[i] = RS[i]; + + // complement RS + for (uint32_t i = 0; i < P25_AUTH_KEY_LENGTH_BYTES; i++) + m_llaCRS[i] = ~m_llaRS[i]; + + // perform crypto + uint8_t* KS = aes->encryptECB(m_llaRS, P25_AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), m_llaK); + ::memcpy(m_llaKS, KS, P25_AUTH_KEY_LENGTH_BYTES); + + if (m_verbose) { + LogMessage(LOG_P25, "P25, generated LLA AM1 parameters"); + } + + delete aes; +} diff --git a/src/p25/Control.h b/src/p25/Control.h index 52bc59f5..7a932b58 100644 --- a/src/p25/Control.h +++ b/src/p25/Control.h @@ -51,6 +51,7 @@ #include #include #include +#include namespace p25 { @@ -220,6 +221,13 @@ namespace p25 uint8_t m_ccFrameCnt; uint8_t m_ccSeq; + std::mt19937 m_random; + + uint8_t* m_llaK; + uint8_t* m_llaRS; + uint8_t* m_llaCRS; + uint8_t* m_llaKS; + NID m_nid; SiteData m_siteData; @@ -260,6 +268,9 @@ namespace p25 void writeRF_Preamble(uint32_t preambleCount = 0, bool force = false); /// Helper to write a P25 TDU packet. void writeRF_TDU(bool noNetwork); + + /// Helper to setup and generate LLA AM1 parameters. + void generateLLA_AM1_Parameters(); }; } // namespace p25 diff --git a/src/p25/P25Defines.h b/src/p25/P25Defines.h index 4626bdc0..94eb6099 100644 --- a/src/p25/P25Defines.h +++ b/src/p25/P25Defines.h @@ -112,6 +112,7 @@ namespace p25 const uint8_t P25_AUTH_RES_LENGTH_BYTES = 4U; const uint8_t P25_AUTH_RAND_SEED_LENGTH_BYTES = 10U; const uint8_t P25_AUTH_RAND_CHLNG_LENGTH_BYTES = 5U; + const uint8_t P25_AUTH_KEY_LENGTH_BYTES = 16U; const uint8_t P25_ALGO_UNENCRYPT = 0x80U; @@ -170,6 +171,7 @@ namespace p25 const uint32_t P25_DENY_RSN_REQ_UNIT_NOT_AUTH = 0x11U; const uint32_t P25_DENY_RSN_TGT_UNIT_NOT_VALID = 0x20U; const uint32_t P25_DENY_RSN_TGT_UNIT_NOT_AUTH = 0x21U; + const uint32_t P25_DENY_RSN_SU_FAILED_AUTH = 0x22U; const uint32_t P25_DENY_RSN_TGT_UNIT_REFUSED = 0x2FU; const uint32_t P25_DENY_RSN_TGT_GROUP_NOT_VALID = 0x30U; const uint32_t P25_DENY_RSN_TGT_GROUP_NOT_AUTH = 0x31U; diff --git a/src/p25/packet/ControlSignaling.cpp b/src/p25/packet/ControlSignaling.cpp index f5022cfe..ad6d09f7 100644 --- a/src/p25/packet/ControlSignaling.cpp +++ b/src/p25/packet/ControlSignaling.cpp @@ -36,6 +36,7 @@ #include "p25/Sync.h" #include "edac/CRC.h" #include "remote/RESTClient.h" +#include "AESCrypto.h" #include "HostMain.h" #include "Log.h" #include "Thread.h" @@ -50,6 +51,7 @@ using namespace p25::packet; #include #include #include +#include // --------------------------------------------------------------------------- // Macros @@ -575,7 +577,12 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetSysId()); + if (m_requireLLAForReg) { + writeRF_TSDU_Auth_Dmd(srcId); + } + else { + writeRF_TSDU_U_Reg_Rsp(srcId, tsbk->getSysId()); + } } break; case TSBK_ISP_LOC_REG_REQ: @@ -587,6 +594,66 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetGroup()); } break; + case TSBK_ISP_AUTH_RESP: + { + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBK_ISP_AUTH_RESP, srcId); + + ISP_AUTH_RESP* isp = static_cast(tsbk.get()); + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u", + tsbk->toString(true).c_str(), srcId); + } + + ::ActivityLog("P25", true, "authentication response from %u", srcId); + + crypto::AES* aes = new crypto::AES(crypto::AESKeyLength::AES_128); + + // get RES1 from response + uint8_t RES1[P25_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; + } + + uint8_t RC[P25_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, P25_AUTH_KEY_LENGTH_BYTES); + for (uint32_t i = 0; i < P25_AUTH_RAND_CHLNG_LENGTH_BYTES; i++) + expandedRAND1[i] = RC[i]; + + // generate XRES1 + uint8_t* XRES1 = aes->encryptECB(expandedRAND1, P25_AUTH_KEY_LENGTH_BYTES * sizeof(uint8_t), m_p25->m_llaKS); + + // compare RES1 and XRES1 + bool authFailed = false; + for (uint32_t i = 0; i < P25_AUTH_RES_LENGTH_BYTES; i++) { + if (XRES1[i] != RES1[i]) { + authFailed = true; + } + } + + if (m_p25->m_ackTSBKRequests) { + writeRF_TSDU_ACK_FNE(srcId, TSBK_ISP_AUTH_RESP, true, true); + } + + if (!authFailed) { + writeRF_TSDU_U_Reg_Rsp(srcId, tsbk->getSysId()); + } + else { + writeRF_TSDU_Deny(P25_WUID_FNE, srcId, P25_DENY_RSN_SU_FAILED_AUTH, TSBK_IOSP_U_REG); + } + } default: LogError(LOG_RF, P25_TSDU_STR ", unhandled LCO, mfId = $%02X, lco = $%02X", tsbk->getMFId(), tsbk->getLCO()); break; @@ -1193,6 +1260,7 @@ ControlSignaling::ControlSignaling(Control* p25, bool dumpTSBKData, bool debug, m_announcementGroup(0xFFFEU), m_verifyAff(false), m_verifyReg(false), + m_requireLLAForReg(false), m_rfMBF(nullptr), m_mbfCnt(0U), m_mbfIdenCnt(0U), @@ -1203,6 +1271,7 @@ ControlSignaling::ControlSignaling(Control* p25, bool dumpTSBKData, bool debug, m_adjSiteUpdateCnt(), m_sccbTable(), m_sccbUpdateCnt(), + m_llaDemandTable(), m_lastMFID(P25_MFG_STANDARD), m_noStatusAck(false), m_noMessageAck(true), @@ -1231,6 +1300,8 @@ ControlSignaling::ControlSignaling(Control* p25, bool dumpTSBKData, bool debug, m_sccbTable.clear(); m_sccbUpdateCnt.clear(); + m_llaDemandTable.clear(); + m_adjSiteUpdateInterval = ADJ_SITE_TIMER_TIMEOUT; m_adjSiteUpdateTimer.setTimeout(m_adjSiteUpdateInterval); m_adjSiteUpdateTimer.start(); @@ -2786,6 +2857,39 @@ bool ControlSignaling::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, return ret; } +/// +/// Helper to write a LLA demand. +/// +/// +void ControlSignaling::writeRF_TSDU_Auth_Dmd(uint32_t srcId) +{ + std::unique_ptr osp = new_unique(MBT_OSP_AUTH_DMD); + osp->setSrcId(srcId); + osp->setAuthRS(m_p25->m_llaRS); + + // generate challenge + uint8_t RC[P25_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); + + ulong64_t challenge = __GET_UINT32(RC, 0U); + challenge = (challenge << 8) + RC[4U]; + + osp->setAuthRC(RC); + + m_llaDemandTable[srcId] = challenge; + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, RC = %X", osp->toString().c_str(), srcId, challenge); + } + + writeRF_TSDU_AMBT(osp.get()); +} + /// /// Helper to write a call termination packet. /// diff --git a/src/p25/packet/ControlSignaling.h b/src/p25/packet/ControlSignaling.h index a7dfb7d4..4d387327 100644 --- a/src/p25/packet/ControlSignaling.h +++ b/src/p25/packet/ControlSignaling.h @@ -112,6 +112,7 @@ namespace p25 bool m_verifyAff; bool m_verifyReg; + bool m_requireLLAForReg; uint8_t* m_rfMBF; uint8_t m_mbfCnt; @@ -127,6 +128,8 @@ namespace p25 std::unordered_map m_sccbTable; std::unordered_map m_sccbUpdateCnt; + std::unordered_map m_llaDemandTable; + uint8_t m_lastMFID; bool m_noStatusAck; @@ -182,7 +185,7 @@ namespace p25 virtual void writeNet_TSDU(lc::TSBK* tsbk); /// Helper to write a multi-block (3-block) P25 TSDU packet. void writeRF_TSDU_MBF(lc::TSBK* tsbk, bool clearBeforeWrite = false); - /// Helper to write a alternate multi-block ControlSignalinging PDU packet. + /// Helper to write a alternate multi-block PDU packet. virtual void writeRF_TSDU_AMBT(lc::AMBT* ambt, bool clearBeforeWrite = false); /* @@ -221,6 +224,9 @@ namespace p25 /// Helper to write a location registration response packet. bool writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, bool grp); + /// Helper to write a LLA demand. + void writeRF_TSDU_Auth_Dmd(uint32_t srcId); + /// Helper to write a call termination packet. bool writeNet_TSDU_Call_Term(uint32_t srcId, uint32_t dstId);