feat(gateway): implement KNX security features including secure session handling and factory certificate management
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_timer.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
@@ -20,6 +22,11 @@ constexpr const char* kFactoryFdskKey = "factory_fdsk";
|
||||
constexpr size_t kFdskSize = 16;
|
||||
constexpr size_t kSerialSize = 6;
|
||||
constexpr size_t kFdskQrSize = 36;
|
||||
constexpr const char* kProductIdentity = "REG1-Dali";
|
||||
constexpr const char* kManufacturerId = "00A4";
|
||||
constexpr const char* kApplicationNumber = "01";
|
||||
constexpr const char* kApplicationVersion = "05";
|
||||
constexpr const char* kDevelopmentStorage = "plain_nvs_development";
|
||||
constexpr uint8_t kCrc4Tab[16] = {
|
||||
0x0, 0x3, 0x6, 0x5, 0xc, 0xf, 0xa, 0x9,
|
||||
0xb, 0x8, 0xd, 0xe, 0x7, 0x4, 0x1, 0x2,
|
||||
@@ -27,6 +34,8 @@ constexpr uint8_t kCrc4Tab[16] = {
|
||||
constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
constexpr char kHexAlphabet[] = "0123456789ABCDEF";
|
||||
|
||||
extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak));
|
||||
|
||||
bool ensureNvsReady() {
|
||||
const esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
@@ -54,6 +63,75 @@ void generateKey(uint8_t* data) {
|
||||
} while (!plausibleKey(data));
|
||||
}
|
||||
|
||||
void clearOpenKnxFdskCache() {
|
||||
if (knx_platform_clear_cached_fdsk != nullptr) {
|
||||
knx_platform_clear_cached_fdsk();
|
||||
}
|
||||
}
|
||||
|
||||
int fromHexDigit(char value) {
|
||||
if (value >= '0' && value <= '9') {
|
||||
return value - '0';
|
||||
}
|
||||
if (value >= 'a' && value <= 'f') {
|
||||
return value - 'a' + 10;
|
||||
}
|
||||
if (value >= 'A' && value <= 'F') {
|
||||
return value - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool parseHexKey(const std::string& value, uint8_t* out) {
|
||||
std::string digits;
|
||||
digits.reserve(value.size());
|
||||
for (char ch : value) {
|
||||
if (ch == ':' || ch == '-' || ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (fromHexDigit(ch) < 0) {
|
||||
return false;
|
||||
}
|
||||
digits.push_back(ch);
|
||||
}
|
||||
if (digits.size() != kFdskSize * 2U) {
|
||||
return false;
|
||||
}
|
||||
for (size_t index = 0; index < kFdskSize; ++index) {
|
||||
const int hi = fromHexDigit(digits[index * 2U]);
|
||||
const int lo = fromHexDigit(digits[index * 2U + 1U]);
|
||||
if (hi < 0 || lo < 0) {
|
||||
return false;
|
||||
}
|
||||
out[index] = static_cast<uint8_t>((hi << 4) | lo);
|
||||
}
|
||||
return plausibleKey(out);
|
||||
}
|
||||
|
||||
bool storeFactoryFdsk(const uint8_t* data) {
|
||||
if (data == nullptr || !plausibleKey(data) || !ensureNvsReady()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t err = nvs_open(kNamespace, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
err = nvs_set_blob(handle, kFactoryFdskKey, data, kFdskSize);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(handle);
|
||||
}
|
||||
nvs_close(handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to store KNX factory FDSK: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
clearOpenKnxFdskCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t crc4Array(const uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
@@ -121,6 +199,21 @@ std::string formatFdskLabel(const std::string& qr_code) {
|
||||
return label;
|
||||
}
|
||||
|
||||
std::string fnv1aHex(const std::string& value) {
|
||||
uint32_t hash = 2166136261u;
|
||||
for (unsigned char ch : value) {
|
||||
hash ^= ch;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
std::array<uint8_t, 4> bytes{
|
||||
static_cast<uint8_t>((hash >> 24) & 0xff),
|
||||
static_cast<uint8_t>((hash >> 16) & 0xff),
|
||||
static_cast<uint8_t>((hash >> 8) & 0xff),
|
||||
static_cast<uint8_t>(hash & 0xff),
|
||||
};
|
||||
return toHex(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gateway::openknx {
|
||||
@@ -174,6 +267,68 @@ FactoryFdskInfo LoadFactoryFdskInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
generateKey(key.data());
|
||||
const bool stored = storeFactoryFdsk(key.data());
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
if (info != nullptr) {
|
||||
*info = LoadFactoryFdskInfo();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
if (!parseHexKey(hex_key, key.data())) {
|
||||
return false;
|
||||
}
|
||||
const bool stored = storeFactoryFdsk(key.data());
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
if (info != nullptr) {
|
||||
*info = LoadFactoryFdskInfo();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResetFactoryFdskCache(FactoryFdskInfo* info) {
|
||||
clearOpenKnxFdskCache();
|
||||
const auto loaded = LoadFactoryFdskInfo();
|
||||
if (info != nullptr) {
|
||||
*info = loaded;
|
||||
}
|
||||
return loaded.available;
|
||||
}
|
||||
|
||||
FactoryCertificatePayload BuildFactoryCertificatePayload() {
|
||||
FactoryCertificatePayload payload;
|
||||
const auto info = LoadFactoryFdskInfo();
|
||||
if (!info.available) {
|
||||
return payload;
|
||||
}
|
||||
payload.available = true;
|
||||
payload.productIdentity = kProductIdentity;
|
||||
payload.manufacturerId = kManufacturerId;
|
||||
payload.applicationNumber = kApplicationNumber;
|
||||
payload.applicationVersion = kApplicationVersion;
|
||||
payload.serialNumber = info.serialNumber;
|
||||
payload.fdskLabel = info.label;
|
||||
payload.fdskQrCode = info.qrCode;
|
||||
payload.storage = kDevelopmentStorage;
|
||||
payload.createdAt = "uptime_us:" + std::to_string(esp_timer_get_time());
|
||||
payload.checksum = fnv1aHex(payload.productIdentity + "|" + payload.manufacturerId + "|" +
|
||||
payload.applicationNumber + "|" + payload.applicationVersion + "|" +
|
||||
payload.serialNumber + "|" + payload.fdskLabel + "|" +
|
||||
payload.fdskQrCode + "|" + payload.createdAt);
|
||||
return payload;
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
|
||||
extern "C" bool knx_platform_get_fdsk(uint8_t* data, size_t len) {
|
||||
|
||||
Reference in New Issue
Block a user