initial support for loading KFDtool EKC files (this is currently unused but is a future stepping-stone for encryption key handling);

pull/86/head
Bryan Biedenkapp 11 months ago
parent 69f8fbe884
commit 78f034511f

@ -117,6 +117,19 @@ master:
# Flag indicating whether TSBK/CSBK/RCCH messages will be logged to InfluxDB.
influxLogRawData: false
#
# Crypto Container Configuration
#
crypto_container:
# Flag indicating whether or not crypto services are enabled.
enabled: false
# Full path to the talkgroup rules file.
file: key_container.ekc
# Container password.
password: "PASSWORD"
# Amount of time between updates of crypto container file. (minutes)
time: 30
#
# Talkgroup Rules Configuration
#

@ -18,6 +18,7 @@ file(GLOB dvmfne_SRC
"src/fne/network/influxdb/*.h"
"src/fne/network/*.h"
"src/fne/network/*.cpp"
"src/fne/xml/*.h"
"src/fne/*.h"
"src/fne/*.cpp"
)

@ -0,0 +1,571 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Converged FNE Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
#include "common/AESCrypto.h"
#include "common/Log.h"
#include "common/Timer.h"
#include "common/Utils.h"
#include "common/zlib/zlib.h"
#include "xml/rapidxml.h"
#include "CryptoContainer.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#if defined(ENABLE_TCP_SSL)
#include <openssl/bio.h>
#include <openssl/evp.h>
#endif
using namespace crypto;
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define CHUNK 16384
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
/**
* @brief Calculates the length of a decoded base64 string.
* @param b64input String containing the base64 encoded data.
* @returns int Length of buffer to contain base64 encoded data.
*/
int calcDecodeLength(const char* b64input)
{
int len = strlen(b64input);
int padding = 0;
// last two chars are =
if (b64input[len-1] == '=' && b64input[len-2] == '=')
padding = 2;
else if (b64input[len-1] == '=') // last char is =
padding = 1;
return (int)len * 0.75 - padding;
}
/**
* @brief Decodes a base64 encoded string.
* @param b64message String containing the base64 encoded data.
* @param buffer Buffer pointer to place encoded data.
* @returns int
*/
int base64Decode(char* b64message, uint8_t** buffer)
{
int decodeLen = calcDecodeLength(b64message), len = 0;
*buffer = (uint8_t*)malloc(decodeLen + 1);
FILE* stream = ::fmemopen(b64message, ::strlen(b64message), "r");
BIO* b64 = BIO_new(BIO_f_base64());
BIO* bio = BIO_new_fp(stream, BIO_NOCLOSE);
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // do not use newlines to flush buffer
len = BIO_read(bio, *buffer, ::strlen(b64message));
// can test here if len == decodeLen - if not, then return an error
(*buffer)[len] = '\0';
BIO_free_all(bio);
::fclose(stream);
return decodeLen;
}
/**
* @brief
* @param buffer
* @param len
* @param target
* @return int
*/
int findFirstChar(const uint8_t* buffer, uint32_t len, char target)
{
for (uint32_t i = 0U; i < len; i++) {
if (buffer[i] == target) {
return (int)i;
}
}
return -1;
}
/**
* @brief
* @param buffer
* @param len
* @param target
* @return int
*/
int findLastChar(const uint8_t* buffer, uint32_t len, char target)
{
if (buffer == nullptr) {
return -1;
}
int lastIndex = -1;
for (uint32_t i = 0U; i < len; i++) {
if (buffer[i] == target) {
lastIndex = i;
}
}
return lastIndex;
}
// ---------------------------------------------------------------------------
// Static Class Members
// ---------------------------------------------------------------------------
std::mutex CryptoContainer::m_mutex;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/* Initializes a new instance of the CryptoContainer class. */
CryptoContainer::CryptoContainer(const std::string& filename, const std::string& password, uint32_t reloadTime, bool enabled) : Thread(),
m_file(filename),
m_password(password),
m_reloadTime(reloadTime),
#if !defined(ENABLE_TCP_SSL)
m_enabled(false),
#else
m_enabled(enabled),
#endif // !ENABLE_TCP_SSL
m_stop(false),
m_keys()
{
/* stub */
}
/* Finalizes a instance of the CryptoContainer class. */
CryptoContainer::~CryptoContainer() = default;
/* Thread entry point. This function is provided to run the thread for the lookup table. */
void CryptoContainer::entry()
{
if (m_reloadTime == 0U) {
return;
}
Timer timer(1U, 60U * m_reloadTime);
timer.start();
while (!m_stop) {
sleep(1000U);
timer.clock();
if (timer.hasExpired()) {
load();
timer.start();
}
}
}
/* Stops and unloads this lookup table. */
void CryptoContainer::stop(bool noDestroy)
{
if (!m_enabled)
return;
if (m_reloadTime == 0U) {
if (!noDestroy)
delete this;
return;
}
m_stop = true;
wait();
}
/* Reads the lookup table from the specified lookup table file. */
bool CryptoContainer::read()
{
if (!m_enabled)
return false;
bool ret = load();
if (m_reloadTime > 0U)
run();
setName("fne:crypto-lookup-tbl");
return ret;
}
/* Clears all entries from the lookup table. */
void CryptoContainer::clear()
{
std::lock_guard<std::mutex> lock(m_mutex);
m_keys.clear();
}
/* Adds a new entry to the lookup table by the specified unique ID. */
void CryptoContainer::addEntry(KeyItem key)
{
if (key.isInvalid())
return;
KeyItem entry = key;
uint32_t id = entry.id();
uint32_t kId = entry.kId();
std::lock_guard<std::mutex> lock(m_mutex);
auto it = std::find_if(m_keys.begin(), m_keys.end(),
[&](KeyItem x)
{
return x.id() == id && x.kId() == kId;
});
if (it != m_keys.end()) {
m_keys[it - m_keys.begin()] = entry;
}
else {
m_keys.push_back(entry);
}
}
/* Erases an existing entry from the lookup table by the specified unique ID. */
void CryptoContainer::eraseEntry(uint32_t id)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = std::find_if(m_keys.begin(), m_keys.end(), [&](KeyItem x) { return x.id() == id; });
if (it != m_keys.end()) {
m_keys.erase(it);
}
}
/* Finds a table entry in this lookup table. */
KeyItem CryptoContainer::find(uint32_t kId)
{
KeyItem entry;
std::lock_guard<std::mutex> lock(m_mutex);
auto it = std::find_if(m_keys.begin(), m_keys.end(),
[&](KeyItem x)
{
return x.kId() == kId;
});
if (it != m_keys.end()) {
entry = *it;
} else {
entry = KeyItem();
}
return entry;
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/* Loads the table from the passed lookup table file. */
bool CryptoContainer::load()
{
#if !defined(ENABLE_TCP_SSL)
return false;
#else
if (!m_enabled) {
return false;
}
if (m_file.length() <= 0) {
return false;
}
if (m_password.length() <= 0) {
return false;
}
FILE* ekcFile = ::fopen(m_file.c_str(), "rb");
if (ekcFile == nullptr) {
LogError(LOG_HOST, "Cannot open the crypto container file - %s", m_file.c_str());
return false;
}
// inflate file
// compression structures
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// set input data
strm.avail_in = 0U;
strm.next_in = Z_NULL;
// initialize decompression
int ret = inflateInit2(&strm, 16 + MAX_WBITS);
if (ret != Z_OK) {
LogError(LOG_HOST, "Error initializing ZLIB, ret = %d", ret);
::fclose(ekcFile);
return false;
}
// skip 4 bytes (C# adds a header on the GZIP stream for the decompressed length)
::fseek(ekcFile, 4, SEEK_SET);
// decompress data
std::vector<uint8_t> decompressedData;
uint8_t inBuffer[CHUNK];
uint8_t outBuffer[CHUNK];
do {
strm.avail_in = fread(inBuffer, 1, CHUNK, ekcFile);
if (::ferror(ekcFile)) {
inflateEnd(&strm);
::fclose(ekcFile);
return false;
}
if (strm.avail_in == 0)
break;
strm.next_in = inBuffer;
uint32_t have = 0U;
do {
strm.avail_out = CHUNK;
strm.next_out = outBuffer;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret == Z_DATA_ERROR) {
// deflate stream invalid
LogError(LOG_HOST, "Error decompressing EKC: %s", (strm.msg == NULL) ? "compressed data error" : strm.msg);
inflateEnd(&strm);
::fclose(ekcFile);
return false;
}
if (ret == Z_STREAM_ERROR || ret < 0) {
LogError(LOG_HOST, "Error decompressing EKC, ret = %d", ret);
inflateEnd(&strm);
::fclose(ekcFile);
return false;
}
have = CHUNK - strm.avail_out;
decompressedData.insert(decompressedData.end(), outBuffer, outBuffer + have);
} while (strm.avail_out == 0);
} while (ret != Z_STREAM_END);
// cleanup
inflateEnd(&strm);
::fclose(ekcFile);
try {
// ensure zero termination
decompressedData.push_back(0U);
uint8_t* decompressed = decompressedData.data();
// parse outer container DOM
enum { PARSE_FLAGS = rapidxml::parse_full };
rapidxml::xml_document<> ekcOuterContainer;
ekcOuterContainer.parse<PARSE_FLAGS>(reinterpret_cast<char*>(decompressed));
rapidxml::xml_node<>* outerRoot = ekcOuterContainer.first_node("OuterContainer");
if (outerRoot != nullptr) {
// get EKC version
std::string version = "";
rapidxml::xml_attribute<>* versionAttr = outerRoot->first_attribute("version");
if (versionAttr != nullptr)
version = std::string(versionAttr->value());
// validate EKC version is set and is 1.0
if (version == "") {
::LogError(LOG_HOST, "Error opening EKC: incorrect version, expected 1.0 got none");
return false;
}
if (version != "1.0") {
::LogError(LOG_HOST, "Error opening EKC: incorrect version, expected 1.0 got %s", version.c_str());
return false;
}
// get key derivation node
rapidxml::xml_node<>* keyDerivation = outerRoot->first_node("KeyDerivation");
if (keyDerivation == nullptr) {
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
return false;
}
// retreive and parse salt
uint8_t* salt = nullptr;
rapidxml::xml_node<>* saltNode = keyDerivation->first_node("Salt");
if (saltNode == nullptr) {
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
return false;
}
int8_t saltBufLen = base64Decode(saltNode->value(), &salt);
// retrieve interation count
int32_t iterationCount = 0;
rapidxml::xml_node<>* iterNode = keyDerivation->first_node("IterationCount");
if (iterNode == nullptr) {
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
return false;
}
iterationCount = ::strtoul(iterNode->value(), NULL, 10);
// retrieve key length
int32_t keyLength = 0;
rapidxml::xml_node<>* keyLenNode = keyDerivation->first_node("KeyLength");
if (keyLenNode == nullptr) {
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
return false;
}
keyLength = ::strtoul(keyLenNode->value(), NULL, 10);
// generate crypto key to decrypt inner container
uint8_t key[EVP_MAX_KEY_LENGTH];
::memset(key, 0x00U, EVP_MAX_KEY_LENGTH);
uint8_t iv[EVP_MAX_IV_LENGTH];
::memset(iv, 0x00U, EVP_MAX_IV_LENGTH);
uint8_t keyIv[EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH];
::memset(keyIv, 0x00U, EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH);
if (PKCS5_PBKDF2_HMAC(m_password.c_str(), m_password.size(), salt, saltBufLen, iterationCount, EVP_sha512(), keyLength + EVP_MAX_IV_LENGTH, keyIv)) {
::memcpy(key, keyIv, keyLength);
::memcpy(iv, keyIv + keyLength, EVP_MAX_IV_LENGTH);
}
// get inner container encrypted data
// bryanb: annoying levels of XML encapsulation...
rapidxml::xml_node<>* encryptedDataNode = outerRoot->first_node("EncryptedData");
if (encryptedDataNode == nullptr) {
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
return false;
}
rapidxml::xml_node<>* cipherDataNode = encryptedDataNode->first_node("CipherData");
if (cipherDataNode == nullptr) {
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
return false;
}
rapidxml::xml_node<>* cipherValue = cipherDataNode->first_node("CipherValue");
if (cipherValue == nullptr) {
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
return false;
}
uint8_t* innerContainerCrypted = nullptr;
int innerContainerLen = base64Decode(cipherValue->value(), &innerContainerCrypted);
// decrypt inner container
AES aes = AES(AESKeyLength::AES_256);
uint8_t* innerContainer = aes.decryptCBC(innerContainerCrypted, innerContainerLen, key, iv);
/*
** bryanb: this is probably slightly error prone...
*/
int xmlFirstTagChar = findFirstChar(innerContainer, innerContainerLen, '<');
int xmlLastTagChar = findLastChar(innerContainer, innerContainerLen, '>');
// zero all bytes after the last > character
::memset(innerContainer + xmlLastTagChar + 1U, 0x00U, innerContainerLen - xmlLastTagChar);
rapidxml::xml_document<> ekcInnerContainer;
ekcInnerContainer.parse<PARSE_FLAGS>(reinterpret_cast<char*>(innerContainer + xmlFirstTagChar));
rapidxml::xml_node<>* innerRoot = ekcInnerContainer.first_node("InnerContainer");
if (innerRoot != nullptr) {
// clear table
clear();
std::lock_guard<std::mutex> lock(m_mutex);
// get keys node
rapidxml::xml_node<>* keys = innerRoot->first_node("Keys");
if (keys != nullptr) {
uint32_t i = 0U;
for (rapidxml::xml_node<>* keyNode = keys->first_node("KeyItem"); keyNode; keyNode = keyNode->next_sibling()) {
KeyItem key = KeyItem();
key.id(i);
// get name
rapidxml::xml_node<>* nameNode = keyNode->first_node("Name");
if (nameNode == nullptr) {
continue;
}
key.name(nameNode->value());
// get keyset ID
rapidxml::xml_node<>* keysetIdNode = keyNode->first_node("KeysetId");
if (keysetIdNode == nullptr) {
continue;
}
key.keysetId(::strtoul(keysetIdNode->value(), NULL, 10));
// get SLN
rapidxml::xml_node<>* slnNode = keyNode->first_node("Sln");
if (slnNode == nullptr) {
continue;
}
key.sln(::strtoul(slnNode->value(), NULL, 10));
// get algorithm ID
rapidxml::xml_node<>* algIdNode = keyNode->first_node("AlgorithmId");
if (algIdNode == nullptr) {
continue;
}
key.algId(::strtoul(algIdNode->value(), NULL, 10));
// get key ID
rapidxml::xml_node<>* kIdNode = keyNode->first_node("KeyId");
if (kIdNode == nullptr) {
continue;
}
key.kId(::strtoul(kIdNode->value(), NULL, 10));
// get key material
rapidxml::xml_node<>* keyMatNode = keyNode->first_node("Key");
if (keyMatNode == nullptr) {
continue;
}
key.keyMaterial(keyMatNode->value());
::LogInfoEx(LOG_HOST, "Key NAME: %s SLN: %u ALGID: $%02X, KID: $%04X", key.name().c_str(), key.sln(), key.algId(), key.kId());
m_keys.push_back(key);
i++;
}
}
}
}
} catch(const std::exception& e) {
::LogError(LOG_HOST, "Error opening EKC: %s", e.what());
return false;
}
if (m_keys.size() == 0U) {
::LogError(LOG_HOST, "No encryption keys defined!");
return false;
}
size_t size = m_keys.size();
if (size == 0U)
return false;
LogInfoEx(LOG_HOST, "Loaded %lu entries into crypto lookup table", size);
return true;
#endif // !ENABLE_TCP_SSL
}

@ -0,0 +1,246 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Converged FNE Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file CryptoContainer.h
* @ingroup fne
* @file CryptoContainer.cpp
* @ingroup fne
*/
#if !defined(__CRYPTO_CONTAINER_H__)
#define __CRYPTO_CONTAINER_H__
#include "common/Defines.h"
#include "common/Thread.h"
#include "common/Utils.h"
#include <string>
#include <mutex>
#include <unordered_map>
#include <vector>
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents an key item.
* @ingroup fne
*/
class HOST_SW_API KeyItem {
public:
/**
* @brief Initializes a new instance of the KeyItem class.
*/
KeyItem() :
m_id(0U),
m_name(),
m_keysetId(0U),
m_sln(0U),
m_algId(0U),
m_kId(0U),
m_keyMaterial()
{
/* stub */
}
/**
* @brief Equals operator. Copies this KeyItem to another KeyItem.
* @param data Instance of KeyItem to copy.
*/
virtual KeyItem& operator= (const KeyItem& data)
{
if (this != &data) {
m_id = data.m_id;
m_name = data.m_name;
m_keysetId = data.m_keysetId;
m_sln = data.m_sln;
m_algId = data.m_algId;
m_kId = data.m_kId;
m_keyMaterial = data.m_keyMaterial;
}
return *this;
}
/**
* @brief Helper to quickly determine if a key item entry is valid.
* @returns bool True, if key item entry is valid, otherwise false.
*/
bool isInvalid() const
{
if (m_sln == 0U)
return true;
if (m_algId == 0U)
return true;
if (m_kId == 0U)
return true;
if (m_keyMaterial.size() == 0U)
return true;
return false;
}
/**
* @brief Gets the encryption key.
* @param[out] key Buffer containing the key.
*/
void getKey(uint8_t* key) const
{
assert(key != nullptr);
const char* rawKey = m_keyMaterial.c_str();
::memset(key, 0x00U, 32U);
for (uint8_t i = 0U; i < 32U; i++) {
char t[4] = {rawKey[0], rawKey[1], 0};
key[i] = (uint8_t)::strtoul(t, NULL, 16);
if (i + 2U > m_keyMaterial.size())
break;
rawKey += 2 * sizeof(char);
}
}
public:
/**
* @brief
*/
__PROPERTY_PLAIN(uint32_t, id);
/**
* @brief
*/
__PROPERTY_PLAIN(std::string, name);
/**
* @brief
*/
__PROPERTY_PLAIN(uint32_t, keysetId);
/**
* @brief
*/
__PROPERTY_PLAIN(uint32_t, sln);
/**
* @brief Encryption algorithm ID.
*/
__PROPERTY_PLAIN(uint8_t, algId);
/**
* @brief Encryption key ID.
*/
__PROPERTY_PLAIN(uint32_t, kId);
/**
* @brief Encryption key material.
*/
__PROPERTY_PLAIN(std::string, keyMaterial);
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements a threading lookup table class that contains routing
* rules information.
* @ingroup lookups_tgid
*/
class HOST_SW_API CryptoContainer : public Thread {
public:
/**
* @brief Initializes a new instance of the CryptoContainer class.
* @param filename Full-path to the crypto container file.
* @param password Crypto container file access password.
* @param reloadTime Interval of time to reload the crypto container.
* @param enabled Flag indicating if crypto container is enabled.
*/
CryptoContainer(const std::string& filename, const std::string& password, uint32_t reloadTime, bool enabled);
/**
* @brief Finalizes a instance of the CryptoContainer class.
*/
~CryptoContainer() override;
/**
* @brief Thread entry point. This function is provided to run the thread
* for the lookup table.
*/
void entry() override;
/**
* @brief Stops and unloads this lookup table.
* @param noDestroy Flag indicating the lookup table should remain resident in memory after stopping.
*/
void stop(bool noDestroy = false);
/**
* @brief Reads the lookup table from the specified lookup table file.
* @returns bool True, if lookup table was read, otherwise false.
*/
bool read();
/**
* @brief Reads the lookup table from the specified lookup table file.
* @returns bool True, if lookup table was read, otherwise false.
*/
bool reload() { return load(); }
/**
* @brief Clears all entries from the lookup table.
*/
void clear();
/**
* @brief Adds a new entry to the lookup table.
* @param key Key Item.
*/
void addEntry(KeyItem key);
/**
* @brief Erases an existing entry from the lookup table by the specified unique ID.
* @param id Unique ID to erase.
*/
void eraseEntry(uint32_t id);
/**
* @brief Finds a table entry in this lookup table.
* @param kId Unique identifier for table entry.
* @returns KeyItem Table entry.
*/
virtual KeyItem find(uint32_t kId);
/**
* @brief Helper to return the flag indicating whether or not the crypto container is enabled.
* @returns bool
*/
bool isEnabled() const { return m_enabled; }
/**
* @brief Helper to set the reload time of this lookup table.
* @param reloadTime Lookup time in seconds.
*/
void setReloadTime(uint32_t reloadTime) { m_reloadTime = reloadTime; }
private:
std::string m_file;
std::string m_password;
uint32_t m_reloadTime;
bool m_enabled;
bool m_stop;
static std::mutex m_mutex;
/**
* @brief Loads the table from the passed lookup table file.
* @return True, if lookup table was loaded, otherwise false.
*/
bool load();
public:
/**
* @brief List of keys.
*/
__PROPERTY_PLAIN(std::vector<KeyItem>, keys);
};
#endif // __CRYPTO_CONTAINER_H__

@ -64,6 +64,7 @@ HostFNE::HostFNE(const std::string& confFile) :
m_ridLookup(nullptr),
m_tidLookup(nullptr),
m_peerListLookup(nullptr),
m_cryptoLookup(nullptr),
m_peerNetworks(),
m_pingTime(5U),
m_maxMissedPings(5U),
@ -353,6 +354,12 @@ bool HostFNE::readParams()
std::string talkgroupConfig = talkgroupRules["file"].as<std::string>();
uint32_t talkgroupConfigReload = talkgroupRules["time"].as<uint32_t>(30U);
yaml::Node cryptoContainer = masterConf["crypto_container"];
bool cryptoContainerEnabled = cryptoContainer["enabled"].as<bool>(false);
std::string cryptoContainerEKC = cryptoContainer["file"].as<std::string>();
std::string cryptoContainerPassword = cryptoContainer["password"].as<std::string>();
uint32_t cryptoContainerReload = cryptoContainer["time"].as<uint32_t>(30U);
std::string peerListLookupFile = systemConf["peer_acl"]["file"].as<std::string>();
bool peerListLookupEnable = systemConf["peer_acl"]["enabled"].as<bool>(false);
std::string peerListModeStr = systemConf["peer_acl"]["mode"].as<std::string>("whitelist");
@ -385,6 +392,16 @@ bool HostFNE::readParams()
m_peerListLookup = new PeerListLookup(peerListLookupFile, peerListMode, peerListConfigReload, peerListLookupEnable);
m_peerListLookup->read();
// try to load peer whitelist/blacklist
LogInfo("Crypto Container Lookups");
LogInfo(" Enabled: %s", cryptoContainerEnabled ? "yes" : "no");
LogInfo(" File: %s", cryptoContainerEKC.length() > 0U ? cryptoContainerEKC.c_str() : "None");
if (cryptoContainerReload > 0U)
LogInfo(" Reload: %u mins", cryptoContainerReload);
m_cryptoLookup = new CryptoContainer(cryptoContainerEKC, cryptoContainerPassword, cryptoContainerReload, cryptoContainerEnabled);
m_cryptoLookup->read();
return true;
}

@ -27,6 +27,7 @@
#include "network/DiagNetwork.h"
#include "network/PeerNetwork.h"
#include "network/RESTAPI.h"
#include "CryptoContainer.h"
#include <string>
#include <unordered_map>
@ -104,6 +105,8 @@ private:
lookups::TalkgroupRulesLookup* m_tidLookup;
lookups::PeerListLookup* m_peerListLookup;
CryptoContainer* m_cryptoLookup;
std::unordered_map<std::string, network::PeerNetwork*> m_peerNetworks;
uint32_t m_pingTime;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,467 @@
// SPDX-License-Identifier: MIT
/*
* Digital Voice Modem - Common Library
* MIT Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2006, 2009 Marcin Kalicinski
* Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
*
*/
/**
* @defgroup xml Extensible Markup Langauge
* @brief Defines and implements XML handling.
* @ingroup common
*
* @file rapidxml_print.h
* @ingroup xml
*/
#if !defined(__RAPIDXML_PRINT_H__)
#define __RAPIDXML_PRINT_H__
// Copyright (C) 2006, 2009 Marcin Kalicinski
// Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
// Version 1.17
// Revision $DateTime: 2023/09/19 23:27:00 $
//! \file rapidxml_print.h This file contains rapidxml printer implementation
#include "xml/rapidxml.h"
// Only include streams if not disabled
#ifndef RAPIDXML_NO_STREAMS
#include <ostream>
#include <iterator>
#endif
namespace rapidxml
{
///////////////////////////////////////////////////////////////////////
// Printing flags
const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.
///////////////////////////////////////////////////////////////////////
// Internal
//! \cond internal
namespace internal
{
///////////////////////////////////////////////////////////////////////////
// Internal character operations
// Copy characters from given range to given output iterator
template<class OutIt, class Ch>
inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)
{
while (begin != end)
*out++ = *begin++;
return out;
}
// Copy characters from given range to given output iterator and expand
// characters into references (&lt; &gt; &apos; &quot; &amp;)
template<class OutIt, class Ch>
inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)
{
while (begin != end)
{
if (*begin == noexpand)
{
*out++ = *begin; // No expansion, copy character
}
else
{
switch (*begin)
{
case Ch('<'):
*out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');
break;
case Ch('>'):
*out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');
break;
case Ch('\''):
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');
break;
case Ch('"'):
*out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');
break;
case Ch('&'):
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';');
break;
default:
*out++ = *begin; // No expansion, copy character
}
}
++begin; // Step to next character
}
return out;
}
// Fill given output iterator with repetitions of the same character
template<class OutIt, class Ch>
inline OutIt fill_chars(OutIt out, int n, Ch ch)
{
for (int i = 0; i < n; ++i)
*out++ = ch;
return out;
}
// Find character
template<class Ch, Ch ch>
inline bool find_char(const Ch *begin, const Ch *end)
{
while (begin != end)
if (*begin++ == ch)
return true;
return false;
}
///////////////////////////////////////////////////////////////////////////
// Internal printing operations
template<class OutIt, class Ch>
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags);
template<class OutIt, class Ch>
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
// Print node
template<class OutIt, class Ch>
inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
// Print proper node type
switch (node->type())
{
// Document
case node_type::node_document:
out = print_children(out, node, flags, indent);
break;
// Element
case node_type::node_element:
out = print_element_node(out, node, flags, indent);
break;
// Data
case node_type::node_data:
out = print_data_node(out, node, flags, indent);
break;
// CDATA
case node_type::node_cdata:
out = print_cdata_node(out, node, flags, indent);
break;
// Declaration
case node_type::node_declaration:
out = print_declaration_node(out, node, flags, indent);
break;
// Comment
case node_type::node_comment:
out = print_comment_node(out, node, flags, indent);
break;
// Doctype
case node_type::node_doctype:
out = print_doctype_node(out, node, flags, indent);
break;
// Pi
case node_type::node_pi:
out = print_pi_node(out, node, flags, indent);
break;
// Unknown
default:
assert(0);
break;
}
// If indenting not disabled, add line break after node
if (!(flags & print_no_indenting))
*out = Ch('\n'), ++out;
// Return modified iterator
return out;
}
// Print children of the node
template<class OutIt, class Ch>
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())
out = print_node(out, child, flags, indent);
return out;
}
// Print attributes of the node
template<class OutIt, class Ch>
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)
{
for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())
{
if (attribute->name() && attribute->value())
{
// Print attribute name
*out = Ch(' '), ++out;
out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);
*out = Ch('='), ++out;
// Print attribute value using appropriate quote type
if (find_char<Ch, Ch('"')>(attribute->value(), attribute->value() + attribute->value_size()))
{
*out = Ch('\''), ++out;
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out);
*out = Ch('\''), ++out;
}
else
{
*out = Ch('"'), ++out;
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out);
*out = Ch('"'), ++out;
}
}
}
return out;
}
// Print data node
template<class OutIt, class Ch>
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_type::node_data);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
return out;
}
// Print data node
template<class OutIt, class Ch>
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_type::node_cdata);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'); ++out;
*out = Ch('!'); ++out;
*out = Ch('['); ++out;
*out = Ch('C'); ++out;
*out = Ch('D'); ++out;
*out = Ch('A'); ++out;
*out = Ch('T'); ++out;
*out = Ch('A'); ++out;
*out = Ch('['); ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch(']'); ++out;
*out = Ch(']'); ++out;
*out = Ch('>'); ++out;
return out;
}
// Print element node
template<class OutIt, class Ch>
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_type::node_element);
// Print element name and attributes, if any
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
out = copy_chars(node->name(), node->name() + node->name_size(), out);
out = print_attributes(out, node, flags);
// If node is childless
if (node->value_size() == 0 && !node->first_node())
{
// Print childless node tag ending
*out = Ch('/'), ++out;
*out = Ch('>'), ++out;
}
else
{
// Print normal node tag ending
*out = Ch('>'), ++out;
// Test if node contains a single data node only (and no other nodes)
xml_node<Ch> *child = node->first_node();
if (!child)
{
// If node has no children, only print its value without indenting
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
}
else if (child->next_sibling() == 0 && child->type() == node_type::node_data)
{
// If node has a sole data child, only print its value without indenting
out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);
}
else
{
// Print all children with full indenting
if (!(flags & print_no_indenting))
*out = Ch('\n'), ++out;
out = print_children(out, node, flags, indent + 1);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
}
// Print node end
*out = Ch('<'), ++out;
*out = Ch('/'), ++out;
out = copy_chars(node->name(), node->name() + node->name_size(), out);
*out = Ch('>'), ++out;
}
return out;
}
// Print declaration node
template<class OutIt, class Ch>
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
// Print declaration start
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('?'), ++out;
*out = Ch('x'), ++out;
*out = Ch('m'), ++out;
*out = Ch('l'), ++out;
// Print attributes
out = print_attributes(out, node, flags);
// Print declaration end
*out = Ch('?'), ++out;
*out = Ch('>'), ++out;
return out;
}
// Print comment node
template<class OutIt, class Ch>
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_type::node_comment);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('!'), ++out;
*out = Ch('-'), ++out;
*out = Ch('-'), ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch('-'), ++out;
*out = Ch('-'), ++out;
*out = Ch('>'), ++out;
return out;
}
// Print doctype node
template<class OutIt, class Ch>
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_type::node_doctype);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('!'), ++out;
*out = Ch('D'), ++out;
*out = Ch('O'), ++out;
*out = Ch('C'), ++out;
*out = Ch('T'), ++out;
*out = Ch('Y'), ++out;
*out = Ch('P'), ++out;
*out = Ch('E'), ++out;
*out = Ch(' '), ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch('>'), ++out;
return out;
}
// Print pi node
template<class OutIt, class Ch>
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_type::node_pi);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('?'), ++out;
out = copy_chars(node->name(), node->name() + node->name_size(), out);
*out = Ch(' '), ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch('?'), ++out;
*out = Ch('>'), ++out;
return out;
}
}
//! \endcond
///////////////////////////////////////////////////////////////////////////
// Printing
//! Prints XML to given output iterator.
//! \param out Output iterator to print to.
//! \param node Node to be printed. Pass xml_document to print entire document.
//! \param flags Flags controlling how XML is printed.
//! \return Output iterator pointing to position immediately after last character of printed text.
template<class OutIt, class Ch>
inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)
{
return internal::print_node(out, &node, flags, 0);
}
#ifndef RAPIDXML_NO_STREAMS
//! Prints XML to given output stream.
//! \param out Output stream to print to.
//! \param node Node to be printed. Pass xml_document to print entire document.
//! \param flags Flags controlling how XML is printed.
//! \return Output stream.
template<class Ch>
inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)
{
print(std::ostream_iterator<Ch>(out), node, flags);
return out;
}
//! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.
//! \param out Output stream to print to.
//! \param node Node to be printed.
//! \return Output stream.
template<class Ch>
inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)
{
return print(out, node);
}
#endif
}
#endif // __RAPIDXML_PRINT_H__

@ -0,0 +1,141 @@
// SPDX-License-Identifier: MIT
/*
* Digital Voice Modem - Common Library
* MIT Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2006, 2009 Marcin Kalicinski
* Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
*
*/
/**
* @defgroup xml Extensible Markup Langauge
* @brief Defines and implements XML handling.
* @ingroup common
*
* @file rapidxml_utils.h
* @ingroup xml
*/
#if !defined(__RAPIDXML_UTILS_H__)
#define __RAPIDXML_UTILS_H__
// Copyright (C) 2006, 2009 Marcin Kalicinski
// Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
// Version 1.17
// Revision $DateTime: 2023/09/19 23:27:00 $
//! \file rapidxml_utils.h This file contains high-level rapidxml utilities that can be useful
//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective.
#include "xml/rapidxml.h"
#include <vector>
#include <string>
#include <fstream>
#include <stdexcept>
namespace rapidxml
{
//! Represents data loaded from a file
template<class Ch = char>
class file
{
public:
//! Loads file into the memory. Data will be automatically destroyed by the destructor.
//! \param filename Filename to load.
file(const char *filename)
{
using namespace std;
// Open stream
basic_ifstream<Ch> stream(filename, ios::binary);
if (!stream)
throw runtime_error(string("cannot open file ") + filename);
stream.unsetf(ios::skipws);
// Determine stream size
stream.seekg(0, ios::end);
size_t size = stream.tellg();
stream.seekg(0);
// Load data and add terminating 0
m_data.resize(size + 1);
stream.read(&m_data.front(), static_cast<streamsize>(size));
m_data[size] = 0;
}
//! Loads file into the memory. Data will be automatically destroyed by the destructor
//! \param stream Stream to load from
file(std::basic_istream<Ch> &stream)
{
using namespace std;
// Load data and add terminating 0
stream.unsetf(ios::skipws);
m_data.assign(istreambuf_iterator<Ch>(stream), istreambuf_iterator<Ch>());
if (stream.fail() || stream.bad())
throw runtime_error("error reading stream");
m_data.push_back(0);
}
//! Gets file data.
//! \return Pointer to data of file.
Ch *data()
{
return &m_data.front();
}
//! Gets file data.
//! \return Pointer to data of file.
const Ch *data() const
{
return &m_data.front();
}
//! Gets file data size.
//! \return Size of file data, in characters.
std::size_t size() const
{
return m_data.size();
}
private:
std::vector<Ch> m_data; // File data
};
//! Counts children of node. Time complexity is O(n).
//! \return Number of children of node
template<class Ch>
inline std::size_t count_children(xml_node<Ch> *node)
{
xml_node<Ch> *child = node->first_node();
std::size_t count = 0;
while (child)
{
++count;
child = child->next_sibling();
}
return count;
}
//! Counts attributes of node. Time complexity is O(n).
//! \return Number of attributes of node
template<class Ch>
inline std::size_t count_attributes(xml_node<Ch> *node)
{
xml_attribute<Ch> *attr = node->first_attribute();
std::size_t count = 0;
while (attr)
{
++count;
attr = attr->next_attribute();
}
return count;
}
}
#endif // __RAPIDXML_UTILS_H__
Loading…
Cancel
Save

Powered by TurnKey Linux.