Files
gateway/components/gateway_bridge/src/security_storage.cpp
T

714 lines
23 KiB
C++

#include "security_storage.h"
#include "gateway_knx_internal.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_timer.h"
#include "mbedtls/sha256.h"
#include "nvs.h"
#include "nvs_flash.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <string>
namespace {
constexpr const char* kTag = "openknx_sec";
constexpr const char* kNamespace = "knx_sec";
constexpr const char* kOamNamespace = "knx_oam_sec";
constexpr const char* kFactoryFdskKey = "factory_fdsk";
constexpr const char* kIpSecureBackboneKey = "ipsec_backbone";
constexpr const char* kIpSecureDeviceAuthKey = "ipsec_dev_auth";
constexpr const char* kIpSecureTunnelCountKey = "ipsec_tunnels";
constexpr const char* kIpSecureActivatedKey = "ipsec_active";
constexpr const char* kIpSecureRoutingSeqKey = "ipsec_route_seq";
constexpr size_t kFdskSize = 16;
constexpr size_t kSerialSize = 6;
constexpr size_t kFdskQrSize = 36;
constexpr const char* kProductIdentity = "REG1-Dali";
constexpr const char* kOamProductIdentity = "OpenKNX-IP-Router";
constexpr const char* kDevelopmentStorage = "base_mac_derived_plain_nvs_development";
constexpr char kFdskDerivationLabel[] = "DaliMaster REG1-Dali deterministic FDSK v1";
constexpr char kOamFdskDerivationLabel[] = "DaliMaster OAM-IP-Router deterministic FDSK v1";
constexpr uint8_t kCrc4Tab[16] = {
0x0, 0x3, 0x6, 0x5, 0xc, 0xf, 0xa, 0x9,
0xb, 0x8, 0xd, 0xe, 0x7, 0x4, 0x1, 0x2,
};
constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
constexpr char kHexAlphabet[] = "0123456789ABCDEF";
struct FactoryFdskContext {
std::string nvsNamespace;
const char* productIdentity;
const char* derivationLabel;
uint16_t manufacturerId;
uint16_t applicationNumber;
uint8_t applicationVersion;
uint32_t serialMacIncrement;
bool clearOpenKnxCache;
};
const FactoryFdskContext kReg1Context{
kNamespace,
kProductIdentity,
kFdskDerivationLabel,
gateway::knx_internal::kReg1DaliManufacturerId,
gateway::knx_internal::kReg1DaliApplicationNumber,
gateway::knx_internal::kReg1DaliApplicationVersion,
gateway::knx_internal::kReg1DaliSerialMacIncrement,
true};
const FactoryFdskContext kOamContext{
kOamNamespace,
kOamProductIdentity,
kOamFdskDerivationLabel,
gateway::knx_internal::kOamRouterManufacturerId,
gateway::knx_internal::kOamRouterApplicationNumber,
gateway::knx_internal::kOamRouterApplicationVersion,
gateway::knx_internal::kOamRouterSerialMacIncrement,
false};
extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak));
FactoryFdskContext reg1ContextForInstance(uint32_t instance_id) {
FactoryFdskContext context = kReg1Context;
context.serialMacIncrement = gateway::knx_internal::kReg1DaliSerialMacIncrement + instance_id;
context.clearOpenKnxCache = instance_id == 0;
if (instance_id != 0) {
context.nvsNamespace = std::string(kNamespace) + "_" + std::to_string(instance_id);
}
return context;
}
std::string hexValue(uint32_t value, int width) {
std::array<char, 9> buffer{};
std::snprintf(buffer.data(), buffer.size(), "%0*" PRIX32, width, value);
return buffer.data();
}
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) {
if (nvs_flash_erase() != ESP_OK) {
return false;
}
return nvs_flash_init() == ESP_OK;
}
return err == ESP_OK || err == ESP_ERR_INVALID_STATE;
}
bool plausibleKey(const uint8_t* data) {
const bool all_zero = std::all_of(data, data + kFdskSize, [](uint8_t value) {
return value == 0x00;
});
const bool all_ff = std::all_of(data, data + kFdskSize, [](uint8_t value) {
return value == 0xff;
});
return !all_zero && !all_ff;
}
bool readBaseMac(uint8_t* data) {
if (data == nullptr) {
return false;
}
if (esp_efuse_mac_get_default(data) == ESP_OK) {
return true;
}
return esp_read_mac(data, ESP_MAC_WIFI_STA) == ESP_OK;
}
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 loadKnxSerialNumber(const FactoryFdskContext& context, uint8_t* serial) {
if (serial == nullptr) {
return false;
}
std::array<uint8_t, kSerialSize> mac{};
if (!readBaseMac(mac.data())) {
return false;
}
uint32_t suffix = (static_cast<uint32_t>(mac[2]) << 24) |
(static_cast<uint32_t>(mac[3]) << 16) |
(static_cast<uint32_t>(mac[4]) << 8) |
static_cast<uint32_t>(mac[5]);
suffix += context.serialMacIncrement;
serial[0] = static_cast<uint8_t>((context.manufacturerId >> 8) & 0xff);
serial[1] = static_cast<uint8_t>(context.manufacturerId & 0xff);
serial[2] = static_cast<uint8_t>((suffix >> 24) & 0xff);
serial[3] = static_cast<uint8_t>((suffix >> 16) & 0xff);
serial[4] = static_cast<uint8_t>((suffix >> 8) & 0xff);
serial[5] = static_cast<uint8_t>(suffix & 0xff);
return true;
}
bool deriveFactoryFdskFromSerial(const FactoryFdskContext& context,
const uint8_t* serial, uint8_t* key) {
if (serial == nullptr || key == nullptr) {
return false;
}
const size_t label_len = std::strlen(context.derivationLabel);
std::vector<uint8_t> material(label_len + kSerialSize);
std::copy(context.derivationLabel, context.derivationLabel + label_len, material.begin());
std::copy(serial, serial + kSerialSize, material.begin() + label_len);
std::array<uint8_t, 32> digest{};
if (mbedtls_sha256(material.data(), material.size(), digest.data(), 0) != 0) {
return false;
}
std::copy(digest.begin(), digest.begin() + kFdskSize, key);
if (!plausibleKey(key)) {
key[kFdskSize - 1] ^= 0xA5;
}
return plausibleKey(key);
}
void syncFactoryFdskToNvs(const FactoryFdskContext& context, const uint8_t* data) {
if (data == nullptr || !plausibleKey(data) || !ensureNvsReady()) {
return;
}
std::array<uint8_t, kFdskSize> stored{};
size_t stored_size = stored.size();
nvs_handle_t handle = 0;
esp_err_t err = nvs_open(context.nvsNamespace.c_str(), NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
return;
}
err = nvs_get_blob(handle, kFactoryFdskKey, stored.data(), &stored_size);
if (err == ESP_OK && stored_size == stored.size() &&
std::equal(stored.begin(), stored.end(), data)) {
nvs_close(handle);
return;
}
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 mirror deterministic KNX factory FDSK: %s", esp_err_to_name(err));
return;
}
if (context.clearOpenKnxCache) {
clearOpenKnxFdskCache();
}
}
uint8_t crc4Array(const uint8_t* data, size_t len) {
uint8_t crc = 0;
for (size_t i = 0; i < len; ++i) {
crc = kCrc4Tab[crc ^ (data[i] >> 4)];
crc = kCrc4Tab[crc ^ (data[i] & 0x0f)];
}
return crc;
}
std::string toBase32NoPadding(const uint8_t* data, size_t len) {
std::string result;
result.reserve(((len * 8) + 4) / 5);
uint32_t buffer = 0;
int bits_left = 0;
for (size_t i = 0; i < len; ++i) {
buffer = (buffer << 8) | data[i];
bits_left += 8;
while (bits_left >= 5) {
const uint8_t index = static_cast<uint8_t>((buffer >> (bits_left - 5)) & 0x1f);
result.push_back(kBase32Alphabet[index]);
bits_left -= 5;
}
}
if (bits_left > 0) {
const uint8_t index = static_cast<uint8_t>((buffer << (5 - bits_left)) & 0x1f);
result.push_back(kBase32Alphabet[index]);
}
return result;
}
std::string toHex(const uint8_t* data, size_t len) {
std::string result;
result.reserve(len * 2);
for (size_t i = 0; i < len; ++i) {
result.push_back(kHexAlphabet[(data[i] >> 4) & 0x0f]);
result.push_back(kHexAlphabet[data[i] & 0x0f]);
}
return result;
}
std::string generateFdskQrCode(const uint8_t* serial, const uint8_t* key) {
std::array<uint8_t, kSerialSize + kFdskSize + 1> buffer{};
std::copy(serial, serial + kSerialSize, buffer.begin());
std::copy(key, key + kFdskSize, buffer.begin() + kSerialSize);
buffer[kSerialSize + kFdskSize] = static_cast<uint8_t>((crc4Array(buffer.data(), buffer.size() - 1) << 4) & 0xff);
std::string encoded = toBase32NoPadding(buffer.data(), buffer.size());
if (encoded.size() > kFdskQrSize) {
encoded.resize(kFdskQrSize);
}
return encoded;
}
std::string formatFdskLabel(const std::string& qr_code) {
std::string label;
label.reserve(qr_code.size() + (qr_code.size() / 6));
for (size_t i = 0; i < qr_code.size(); ++i) {
if (i != 0 && (i % 6) == 0) {
label.push_back('-');
}
label.push_back(qr_code[i]);
}
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 {
bool LoadFactoryFdskForContext(const FactoryFdskContext& context, uint8_t* data, size_t len) {
if (data == nullptr || len < kFdskSize) {
return false;
}
std::array<uint8_t, kSerialSize> serial{};
std::array<uint8_t, kFdskSize> key{};
if (!loadKnxSerialNumber(context, serial.data()) ||
!deriveFactoryFdskFromSerial(context, serial.data(), key.data())) {
return false;
}
std::memcpy(data, key.data(), kFdskSize);
syncFactoryFdskToNvs(context, key.data());
return true;
}
FactoryFdskInfo LoadFactoryFdskInfoForContext(const FactoryFdskContext& context) {
FactoryFdskInfo info;
std::array<uint8_t, kFdskSize> key{};
std::array<uint8_t, kSerialSize> serial{};
if (!loadKnxSerialNumber(context, serial.data()) ||
!LoadFactoryFdskForContext(context, key.data(), key.size())) {
return info;
}
info.available = true;
info.serialNumber = toHex(serial.data(), serial.size());
info.qrCode = generateFdskQrCode(serial.data(), key.data());
info.label = formatFdskLabel(info.qrCode);
return info;
}
bool GenerateFactoryFdskForContext(const FactoryFdskContext& context,
FactoryFdskInfo* info) {
std::array<uint8_t, kFdskSize> key{};
const bool stored = LoadFactoryFdskForContext(context, key.data(), key.size());
std::fill(key.begin(), key.end(), 0);
if (!stored) {
return false;
}
if (info != nullptr) {
*info = LoadFactoryFdskInfoForContext(context);
}
return true;
}
bool WriteFactoryFdskHexForContext(const FactoryFdskContext& context,
const std::string& hex_key,
FactoryFdskInfo* info) {
std::array<uint8_t, kFdskSize> key{};
if (!parseHexKey(hex_key, key.data())) {
return false;
}
std::array<uint8_t, kSerialSize> serial{};
std::array<uint8_t, kFdskSize> derived{};
const bool stored = loadKnxSerialNumber(context, serial.data()) &&
deriveFactoryFdskFromSerial(context, serial.data(), derived.data()) &&
std::equal(key.begin(), key.end(), derived.begin());
if (stored) {
syncFactoryFdskToNvs(context, derived.data());
}
std::fill(key.begin(), key.end(), 0);
std::fill(derived.begin(), derived.end(), 0);
if (!stored) {
return false;
}
if (info != nullptr) {
*info = LoadFactoryFdskInfoForContext(context);
}
return true;
}
bool ResetFactoryFdskCacheForContext(const FactoryFdskContext& context,
FactoryFdskInfo* info) {
if (context.clearOpenKnxCache) {
clearOpenKnxFdskCache();
}
const auto loaded = LoadFactoryFdskInfoForContext(context);
if (info != nullptr) {
*info = loaded;
}
return loaded.available;
}
FactoryCertificatePayload BuildFactoryCertificatePayloadForContext(
const FactoryFdskContext& context) {
FactoryCertificatePayload payload;
const auto info = LoadFactoryFdskInfoForContext(context);
if (!info.available) {
return payload;
}
payload.available = true;
payload.productIdentity = context.productIdentity;
payload.manufacturerId = hexValue(context.manufacturerId, 4);
payload.applicationNumber = hexValue(context.applicationNumber,
context.applicationNumber <= 0xff ? 2 : 4);
payload.applicationVersion = hexValue(context.applicationVersion, 2);
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;
}
std::string TunnelUserKeyName(uint8_t index) {
std::array<char, 18> buffer{};
std::snprintf(buffer.data(), buffer.size(), "ipsec_tunnel_%02u",
static_cast<unsigned>(index));
return buffer.data();
}
bool BlobKeyAvailable(nvs_handle_t handle, const char* key) {
std::array<uint8_t, kFdskSize> data{};
size_t len = data.size();
return nvs_get_blob(handle, key, data.data(), &len) == ESP_OK && len == data.size() &&
plausibleKey(data.data());
}
bool SetOptionalHexBlob(nvs_handle_t handle, const char* key, const std::string& hex) {
if (hex.empty()) {
nvs_erase_key(handle, key);
return true;
}
std::array<uint8_t, kFdskSize> data{};
if (!parseHexKey(hex, data.data())) {
return false;
}
const esp_err_t err = nvs_set_blob(handle, key, data.data(), data.size());
std::fill(data.begin(), data.end(), 0);
return err == ESP_OK;
}
bool LoadOptionalKey(nvs_handle_t handle, const char* key, std::array<uint8_t, kFdskSize>* out) {
if (out == nullptr) {
return false;
}
std::array<uint8_t, kFdskSize> data{};
size_t len = data.size();
if (nvs_get_blob(handle, key, data.data(), &len) != ESP_OK || len != data.size() ||
!plausibleKey(data.data())) {
return false;
}
*out = data;
return true;
}
bool LoadFactoryFdsk(uint8_t* data, size_t len) {
return LoadFactoryFdskForContext(kReg1Context, data, len);
}
FactoryFdskInfo LoadFactoryFdskInfo() {
return LoadFactoryFdskInfoForContext(kReg1Context);
}
bool LoadFactoryFdskForInstance(uint32_t instance_id, uint8_t* data, size_t len) {
const auto context = reg1ContextForInstance(instance_id);
return LoadFactoryFdskForContext(context, data, len);
}
FactoryFdskInfo LoadFactoryFdskInfoForInstance(uint32_t instance_id) {
const auto context = reg1ContextForInstance(instance_id);
return LoadFactoryFdskInfoForContext(context);
}
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
return GenerateFactoryFdskForContext(kReg1Context, info);
}
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
return WriteFactoryFdskHexForContext(kReg1Context, hex_key, info);
}
bool ResetFactoryFdskCache(FactoryFdskInfo* info) {
return ResetFactoryFdskCacheForContext(kReg1Context, info);
}
bool ResetFactorySecurityForInstance(uint32_t instance_id, FactoryFdskInfo* info) {
const auto context = reg1ContextForInstance(instance_id);
if (ensureNvsReady()) {
nvs_handle_t handle = 0;
if (nvs_open(context.nvsNamespace.c_str(), NVS_READWRITE, &handle) == ESP_OK) {
nvs_erase_key(handle, kFactoryFdskKey);
nvs_commit(handle);
nvs_close(handle);
}
}
return ResetFactoryFdskCacheForContext(context, info);
}
FactoryCertificatePayload BuildFactoryCertificatePayload() {
return BuildFactoryCertificatePayloadForContext(kReg1Context);
}
FactoryCertificatePayload BuildFactoryCertificatePayloadForInstance(uint32_t instance_id) {
const auto context = reg1ContextForInstance(instance_id);
return BuildFactoryCertificatePayloadForContext(context);
}
bool LoadOamFactoryFdsk(uint8_t* data, size_t len) {
return LoadFactoryFdskForContext(kOamContext, data, len);
}
FactoryFdskInfo LoadOamFactoryFdskInfo() {
return LoadFactoryFdskInfoForContext(kOamContext);
}
bool GenerateOamFactoryFdsk(FactoryFdskInfo* info) {
return GenerateFactoryFdskForContext(kOamContext, info);
}
bool WriteOamFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
return WriteFactoryFdskHexForContext(kOamContext, hex_key, info);
}
bool ResetOamFactoryFdskCache(FactoryFdskInfo* info) {
return ResetFactoryFdskCacheForContext(kOamContext, info);
}
FactoryCertificatePayload BuildOamFactoryCertificatePayload() {
return BuildFactoryCertificatePayloadForContext(kOamContext);
}
IpSecureCredentialStatus LoadOamIpSecureCredentialStatus() {
IpSecureCredentialStatus status;
if (!ensureNvsReady()) {
return status;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READONLY, &handle) != ESP_OK) {
return status;
}
uint8_t activated = 0;
if (nvs_get_u8(handle, kIpSecureActivatedKey, &activated) == ESP_OK) {
status.activated = activated != 0;
}
uint8_t tunnel_count = 0;
if (nvs_get_u8(handle, kIpSecureTunnelCountKey, &tunnel_count) == ESP_OK) {
status.tunnelUserCount = tunnel_count;
}
uint64_t routing_sequence = 0;
if (nvs_get_u64(handle, kIpSecureRoutingSeqKey, &routing_sequence) == ESP_OK) {
status.routingSequence = routing_sequence;
}
status.backboneKeyAvailable = BlobKeyAvailable(handle, kIpSecureBackboneKey);
status.deviceAuthenticationKeyAvailable = BlobKeyAvailable(handle, kIpSecureDeviceAuthKey);
nvs_close(handle);
return status;
}
::gateway::GatewayKnxIpSecureCredentialMaterial LoadOamIpSecureCredentialMaterial() {
::gateway::GatewayKnxIpSecureCredentialMaterial material;
if (!ensureNvsReady()) {
return material;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READONLY, &handle) != ESP_OK) {
return material;
}
uint8_t activated = 0;
if (nvs_get_u8(handle, kIpSecureActivatedKey, &activated) == ESP_OK) {
material.activated = activated != 0;
}
material.backbone_key_available = LoadOptionalKey(handle, kIpSecureBackboneKey,
&material.backbone_key);
material.device_authentication_key_available =
LoadOptionalKey(handle, kIpSecureDeviceAuthKey,
&material.device_authentication_key);
uint8_t tunnel_count = 0;
if (nvs_get_u8(handle, kIpSecureTunnelCountKey, &tunnel_count) == ESP_OK) {
tunnel_count = std::min<uint8_t>(tunnel_count, 16);
material.tunnel_user_keys.reserve(tunnel_count);
for (uint8_t index = 0; index < tunnel_count; ++index) {
std::array<uint8_t, kFdskSize> key{};
const std::string key_name = TunnelUserKeyName(index);
if (LoadOptionalKey(handle, key_name.c_str(), &key)) {
material.tunnel_user_keys.push_back(key);
}
}
}
uint64_t routing_sequence = 0;
if (nvs_get_u64(handle, kIpSecureRoutingSeqKey, &routing_sequence) == ESP_OK) {
material.routing_sequence = routing_sequence;
}
nvs_close(handle);
return material;
}
bool WriteOamIpSecureKeyringHex(const std::string& backbone_key_hex,
const std::vector<std::string>& tunnel_user_key_hex,
const std::string& device_auth_key_hex,
bool activated) {
if (tunnel_user_key_hex.size() > 16 || !ensureNvsReady()) {
return false;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return false;
}
bool ok = SetOptionalHexBlob(handle, kIpSecureBackboneKey, backbone_key_hex) &&
SetOptionalHexBlob(handle, kIpSecureDeviceAuthKey, device_auth_key_hex);
for (uint8_t index = 0; index < 16; ++index) {
const std::string key = TunnelUserKeyName(index);
nvs_erase_key(handle, key.c_str());
}
if (ok) {
for (size_t index = 0; index < tunnel_user_key_hex.size(); ++index) {
const std::string key = TunnelUserKeyName(static_cast<uint8_t>(index));
ok = SetOptionalHexBlob(handle, key.c_str(), tunnel_user_key_hex[index]);
if (!ok) {
break;
}
}
}
if (ok) {
ok = nvs_set_u8(handle, kIpSecureTunnelCountKey,
static_cast<uint8_t>(tunnel_user_key_hex.size())) == ESP_OK &&
nvs_set_u8(handle, kIpSecureActivatedKey, activated ? 1 : 0) == ESP_OK;
}
if (ok) {
uint64_t existing_sequence = 0;
if (nvs_get_u64(handle, kIpSecureRoutingSeqKey, &existing_sequence) != ESP_OK) {
ok = nvs_set_u64(handle, kIpSecureRoutingSeqKey, 0) == ESP_OK;
}
}
if (ok) {
ok = nvs_commit(handle) == ESP_OK;
}
nvs_close(handle);
return ok;
}
bool StoreOamIpSecureRoutingSequence(uint64_t sequence) {
if (!ensureNvsReady()) {
return false;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return false;
}
const bool ok = nvs_set_u64(handle, kIpSecureRoutingSeqKey, sequence) == ESP_OK &&
nvs_commit(handle) == ESP_OK;
nvs_close(handle);
return ok;
}
bool ClearOamIpSecureKeyring() {
if (!ensureNvsReady()) {
return false;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return false;
}
nvs_erase_key(handle, kIpSecureBackboneKey);
nvs_erase_key(handle, kIpSecureDeviceAuthKey);
nvs_erase_key(handle, kIpSecureTunnelCountKey);
nvs_erase_key(handle, kIpSecureActivatedKey);
nvs_erase_key(handle, kIpSecureRoutingSeqKey);
for (uint8_t index = 0; index < 16; ++index) {
const std::string key = TunnelUserKeyName(index);
nvs_erase_key(handle, key.c_str());
}
const bool ok = nvs_commit(handle) == ESP_OK;
nvs_close(handle);
return ok;
}
} // namespace gateway::openknx
extern "C" bool knx_platform_get_fdsk(uint8_t* data, size_t len) {
return gateway::openknx::LoadFactoryFdsk(data, len);
}