Add secure transport and OAM router runtime implementations
- Implement secure transport mechanisms in `gateway_knx_secure_transport.cpp` for handling secure sessions, including AES encryption, session key generation, and secure packet wrapping and unwrapping. - Introduce `OamRouterRuntime` in `oam_router_runtime.cpp` to manage OAM router identity, individual addresses, and tunnel frame handling. - Enhance secure session management with functions for session allocation, authentication, and secure packet processing. - Ensure compatibility with existing KNXnet/IP protocols while adding support for secure communications. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -199,6 +199,22 @@ cJSON* FactoryCertificateToCjson(const openknx::FactoryCertificatePayload& certi
|
||||
return root;
|
||||
}
|
||||
|
||||
cJSON* IpSecureCredentialStatusToCjson(
|
||||
const openknx::IpSecureCredentialStatus& status) {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (root == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
cJSON_AddBoolToObject(root, "activated", status.activated);
|
||||
cJSON_AddBoolToObject(root, "backboneKeyAvailable", status.backboneKeyAvailable);
|
||||
cJSON_AddBoolToObject(root, "deviceAuthenticationKeyAvailable",
|
||||
status.deviceAuthenticationKeyAvailable);
|
||||
cJSON_AddNumberToObject(root, "tunnelUserCount", status.tunnelUserCount);
|
||||
cJSON_AddNumberToObject(root, "routingSequence",
|
||||
static_cast<double>(status.routingSequence));
|
||||
return root;
|
||||
}
|
||||
|
||||
cJSON* SecurityFailuresToCjson() {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (root == nullptr) {
|
||||
@@ -1522,6 +1538,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
[this](uint16_t group_object_number, const uint8_t* data, size_t len) {
|
||||
return service.routeKnxGroupObjectWrite(group_object_number, data, len);
|
||||
});
|
||||
knx_router->setOamIpSecureRoutingSequenceStoreHandler([](uint64_t sequence) {
|
||||
openknx::StoreOamIpSecureRoutingSequence(sequence);
|
||||
});
|
||||
knx_router->setOamIpSecureCredentials(openknx::LoadOamIpSecureCredentialMaterial());
|
||||
if (const auto active_knx = activeKnxConfigLocked(); active_knx.has_value()) {
|
||||
knx->setConfig(active_knx.value());
|
||||
knx_router->setConfig(active_knx.value());
|
||||
@@ -2154,6 +2174,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
runtime_config.individual_address);
|
||||
knx->setConfig(runtime_config);
|
||||
knx_router->setConfig(runtime_config);
|
||||
knx_router->setOamIpSecureCredentials(openknx::LoadOamIpSecureCredentialMaterial());
|
||||
knx_router->setCommissioningOnly(commissioning_only);
|
||||
if (!runtime_config.ip_router_enabled) {
|
||||
knx_started = false;
|
||||
@@ -2218,6 +2239,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
endpoint_runtime = const_cast<GatewayBridgeService&>(service).selectKnxEndpointRuntime();
|
||||
}
|
||||
bool programming_mode = false;
|
||||
bool oam_programming_mode = false;
|
||||
bool programming_control_available = false;
|
||||
int endpoint_owner_gateway_id = -1;
|
||||
if (endpoint_runtime != nullptr) {
|
||||
@@ -2227,6 +2249,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
endpoint_runtime->knx_router->started();
|
||||
if (programming_control_available) {
|
||||
programming_mode = endpoint_runtime->knx_router->programmingMode();
|
||||
oam_programming_mode = endpoint_runtime->knx_router->oamProgrammingMode();
|
||||
}
|
||||
}
|
||||
const auto effective_knx =
|
||||
@@ -2236,6 +2259,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddBoolToObject(knx_json, "started", knx_started);
|
||||
cJSON_AddBoolToObject(knx_json, "routerReady", knx_router != nullptr && knx_router->started());
|
||||
cJSON_AddBoolToObject(knx_json, "programmingMode", programming_mode);
|
||||
cJSON_AddBoolToObject(knx_json, "oamProgrammingMode", oam_programming_mode);
|
||||
cJSON_AddBoolToObject(knx_json, "programmingControlAvailable",
|
||||
programming_control_available);
|
||||
cJSON_AddBoolToObject(knx_json, "endpointOwner",
|
||||
@@ -2265,7 +2289,11 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
#else
|
||||
cJSON_AddBoolToObject(security_json, "knxnetIpSecureServicesRecognized", false);
|
||||
#endif
|
||||
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
|
||||
cJSON_AddBoolToObject(security_json, "knxnetIpSecureImplemented", true);
|
||||
#else
|
||||
cJSON_AddBoolToObject(security_json, "knxnetIpSecureImplemented", false);
|
||||
#endif
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
|
||||
cJSON_AddBoolToObject(security_json, "developmentEndpointsEnabled", true);
|
||||
#else
|
||||
@@ -2294,6 +2322,27 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddItemToObject(security_json, "failures", failures_json);
|
||||
}
|
||||
#endif
|
||||
cJSON* oam_security_json = cJSON_CreateObject();
|
||||
if (oam_security_json != nullptr) {
|
||||
cJSON* oam_fdsk_json = FactoryFdskInfoToCjson(
|
||||
openknx::LoadOamFactoryFdskInfo(), false);
|
||||
if (oam_fdsk_json != nullptr) {
|
||||
cJSON_AddItemToObject(oam_security_json, "factorySetupKey", oam_fdsk_json);
|
||||
}
|
||||
cJSON* oam_certificate_json = FactoryCertificateToCjson(
|
||||
openknx::BuildOamFactoryCertificatePayload(), false);
|
||||
if (oam_certificate_json != nullptr) {
|
||||
cJSON_AddItemToObject(oam_security_json, "factoryCertificate",
|
||||
oam_certificate_json);
|
||||
}
|
||||
cJSON* oam_credentials_json = IpSecureCredentialStatusToCjson(
|
||||
openknx::LoadOamIpSecureCredentialStatus());
|
||||
if (oam_credentials_json != nullptr) {
|
||||
cJSON_AddItemToObject(oam_security_json, "ipSecureCredentials",
|
||||
oam_credentials_json);
|
||||
}
|
||||
cJSON_AddItemToObject(security_json, "oamRouter", oam_security_json);
|
||||
}
|
||||
cJSON_AddItemToObject(knx_json, "security", security_json);
|
||||
}
|
||||
if (effective_knx.has_value()) {
|
||||
@@ -2314,6 +2363,29 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
effective_knx->ip_interface_individual_address);
|
||||
cJSON_AddNumberToObject(knx_json, "individualAddress",
|
||||
effective_knx->individual_address);
|
||||
cJSON* oam_json = ToCjson(
|
||||
GatewayKnxOamRouterConfigToValue(effective_knx->oam_router));
|
||||
if (oam_json != nullptr) {
|
||||
cJSON_AddItemToObject(knx_json, "oamRouter", oam_json);
|
||||
}
|
||||
cJSON* cloud_remote_json = cJSON_CreateObject();
|
||||
if (cloud_remote_json != nullptr) {
|
||||
const auto& cloud_remote = effective_knx->oam_router.cloud_remote;
|
||||
cJSON_AddBoolToObject(cloud_remote_json, "prepared", true);
|
||||
cJSON_AddBoolToObject(cloud_remote_json, "enabled", cloud_remote.enabled);
|
||||
cJSON_AddStringToObject(cloud_remote_json, "mode", cloud_remote.mode.c_str());
|
||||
cJSON_AddBoolToObject(cloud_remote_json, "requireSecureTunnel",
|
||||
cloud_remote.require_secure_tunnel);
|
||||
cJSON_AddBoolToObject(cloud_remote_json, "udpPunchEnabled",
|
||||
cloud_remote.udp_punch_enabled);
|
||||
cJSON_AddBoolToObject(cloud_remote_json, "relayEndpointConfigured",
|
||||
!cloud_remote.relay_endpoint.empty());
|
||||
cJSON_AddBoolToObject(cloud_remote_json, "mqttTopicPrefixConfigured",
|
||||
!cloud_remote.mqtt_topic_prefix.empty());
|
||||
cJSON_AddBoolToObject(cloud_remote_json, "authTokenRefConfigured",
|
||||
!cloud_remote.auth_token_ref.empty());
|
||||
cJSON_AddItemToObject(knx_json, "cloudKnxRemoteAccess", cloud_remote_json);
|
||||
}
|
||||
cJSON* serial_json = cJSON_CreateObject();
|
||||
if (serial_json != nullptr) {
|
||||
cJSON_AddNumberToObject(serial_json, "uartPort", effective_knx->tp_uart.uart_port);
|
||||
@@ -3220,6 +3292,46 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.oam_router.enabled) {
|
||||
if (config.oam_router.individual_address == 0 ||
|
||||
config.oam_router.individual_address == 0xffff) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "OAM KNX/IP router individual address must be a configured address";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.oam_router.tunnel_address_base == 0 ||
|
||||
config.oam_router.tunnel_address_base == 0xffff ||
|
||||
config.oam_router.tunnel_address_base > 0xffef) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "OAM KNX/IP router tunnel address base must leave room for 16 tunnels";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.oam_router.individual_address == config.individual_address ||
|
||||
config.oam_router.individual_address == config.ip_interface_individual_address ||
|
||||
config.oam_router.tunnel_address_base == config.individual_address ||
|
||||
config.oam_router.tunnel_address_base == config.ip_interface_individual_address) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "OAM KNX/IP router addresses must differ from the shared IP interface and KNX-DALI gateway addresses";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.programming_button_gpio >= 0 &&
|
||||
config.programming_button_gpio == config.oam_router.programming_button_gpio) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "OAM and KNX-DALI programming buttons must use separate GPIOs";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config.programming_led_gpio >= 0 &&
|
||||
config.programming_led_gpio == config.oam_router.programming_led_gpio) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "OAM and KNX-DALI programming LEDs must use separate GPIOs";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
if (!config.ip_router_enabled || !GatewayKnxConfigUsesTpUart(config)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -3360,6 +3472,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
if (knx_router != nullptr) {
|
||||
knx_router->setConfig(merged_config);
|
||||
knx_router->setOamIpSecureCredentials(openknx::LoadOamIpSecureCredentialMaterial());
|
||||
knx_router->setCommissioningOnly(false);
|
||||
}
|
||||
if (restart_router) {
|
||||
@@ -4615,6 +4728,40 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
}
|
||||
return handleGet("status", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_oam_programming_mode") {
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "OAM KNX programming mode JSON is required");
|
||||
}
|
||||
const cJSON* enabled_item = cJSON_GetObjectItemCaseSensitive(body_root, "enabled");
|
||||
if (!cJSON_IsBool(enabled_item)) {
|
||||
cJSON_Delete(body_root);
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "boolean enabled field is required");
|
||||
}
|
||||
const bool enabled = cJSON_IsTrue(enabled_item);
|
||||
cJSON_Delete(body_root);
|
||||
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
if (owner == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NOT_FOUND, "no KNX/IP endpoint owner is configured");
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_ERR_INVALID_STATE;
|
||||
std::string detail = "KNX/IP router is unavailable";
|
||||
{
|
||||
LockGuard guard(owner->lock);
|
||||
if (owner->knx_router != nullptr) {
|
||||
err = owner->knx_router->setOamProgrammingMode(enabled);
|
||||
detail = owner->knx_router->lastError();
|
||||
}
|
||||
owner->knx_last_error = err == ESP_OK ? std::string() : detail;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, detail.empty() ? "failed to change OAM KNX programming mode"
|
||||
: detail.c_str());
|
||||
}
|
||||
return handleGet("status", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_security_read_factory_key") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
|
||||
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||
@@ -4807,6 +4954,225 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
#else
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||
"KNX security development endpoints are disabled");
|
||||
#endif
|
||||
}
|
||||
if (action == "knx_oam_security_read_factory_key") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
|
||||
}
|
||||
const char* confirm = JsonString(body_root, "confirm");
|
||||
const bool confirmed = confirm != nullptr &&
|
||||
std::string_view(confirm) == "read-oam-factory-setup-key";
|
||||
cJSON_Delete(body_root);
|
||||
if (!confirmed) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG,
|
||||
"OAM factory setup key read confirmation is required");
|
||||
}
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate OAM security response");
|
||||
}
|
||||
cJSON* fdsk_json = FactoryFdskInfoToCjson(openknx::LoadOamFactoryFdskInfo(), true);
|
||||
if (fdsk_json != nullptr) {
|
||||
cJSON_AddItemToObject(response, "factorySetupKey", fdsk_json);
|
||||
}
|
||||
return JsonOk(response);
|
||||
#else
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||
"KNX security development endpoints are disabled");
|
||||
#endif
|
||||
}
|
||||
if (action == "knx_oam_security_generate_factory_key") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
|
||||
}
|
||||
const char* confirm = JsonString(body_root, "confirm");
|
||||
const bool include_secret = JsonBool(body_root, "includeSecret", false);
|
||||
const bool confirmed = confirm != nullptr &&
|
||||
std::string_view(confirm) == "generate-oam-factory-setup-key";
|
||||
cJSON_Delete(body_root);
|
||||
if (!confirmed) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG,
|
||||
"OAM factory setup key generation confirmation is required");
|
||||
}
|
||||
openknx::FactoryFdskInfo info;
|
||||
if (!openknx::GenerateOamFactoryFdsk(&info)) {
|
||||
return ErrorResponse(ESP_FAIL, "failed to generate OAM factory setup key");
|
||||
}
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate OAM security response");
|
||||
}
|
||||
cJSON* fdsk_json = FactoryFdskInfoToCjson(info, include_secret);
|
||||
if (fdsk_json != nullptr) {
|
||||
cJSON_AddItemToObject(response, "factorySetupKey", fdsk_json);
|
||||
}
|
||||
return JsonOk(response);
|
||||
#else
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||
"KNX security development endpoints are disabled");
|
||||
#endif
|
||||
}
|
||||
if (action == "knx_oam_security_reset_factory_key") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
|
||||
}
|
||||
const char* confirm = JsonString(body_root, "confirm");
|
||||
const bool confirmed = confirm != nullptr &&
|
||||
std::string_view(confirm) == "reset-oam-factory-setup-key";
|
||||
cJSON_Delete(body_root);
|
||||
if (!confirmed) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG,
|
||||
"OAM factory setup key reset confirmation is required");
|
||||
}
|
||||
openknx::FactoryFdskInfo info;
|
||||
if (!openknx::ResetOamFactoryFdskCache(&info)) {
|
||||
return ErrorResponse(ESP_FAIL, "failed to reload OAM factory setup key");
|
||||
}
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate OAM security response");
|
||||
}
|
||||
cJSON* fdsk_json = FactoryFdskInfoToCjson(info, false);
|
||||
if (fdsk_json != nullptr) {
|
||||
cJSON_AddItemToObject(response, "factorySetupKey", fdsk_json);
|
||||
}
|
||||
return JsonOk(response);
|
||||
#else
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||
"KNX security development endpoints are disabled");
|
||||
#endif
|
||||
}
|
||||
if (action == "knx_oam_security_export_factory_certificate") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
|
||||
}
|
||||
const char* confirm = JsonString(body_root, "confirm");
|
||||
const bool confirmed = confirm != nullptr &&
|
||||
std::string_view(confirm) == "export-oam-factory-certificate";
|
||||
cJSON_Delete(body_root);
|
||||
if (!confirmed) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG,
|
||||
"OAM factory certificate export confirmation is required");
|
||||
}
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate OAM security response");
|
||||
}
|
||||
cJSON* certificate_json = FactoryCertificateToCjson(
|
||||
openknx::BuildOamFactoryCertificatePayload(), true);
|
||||
if (certificate_json != nullptr) {
|
||||
cJSON_AddItemToObject(response, "factoryCertificate", certificate_json);
|
||||
}
|
||||
return JsonOk(response);
|
||||
#else
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||
"KNX security development endpoints are disabled");
|
||||
#endif
|
||||
}
|
||||
if (action == "knx_oam_security_upload_keyring") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
|
||||
defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "OAM keyring JSON is required");
|
||||
}
|
||||
const char* confirm = JsonString(body_root, "confirm");
|
||||
const char* backbone_key = JsonString(body_root, "backboneKeyHex");
|
||||
const char* device_auth_key = JsonString(body_root, "deviceAuthenticationKeyHex");
|
||||
const bool activated = JsonBool(body_root, "activated", true);
|
||||
const bool confirmed = confirm != nullptr &&
|
||||
std::string_view(confirm) == "upload-oam-ip-secure-keyring";
|
||||
std::vector<std::string> tunnel_keys;
|
||||
const cJSON* tunnel_array = cJSON_GetObjectItemCaseSensitive(body_root, "tunnelUserKeysHex");
|
||||
if (tunnel_array == nullptr) {
|
||||
tunnel_array = cJSON_GetObjectItemCaseSensitive(body_root, "tunnelUserKeyHex");
|
||||
}
|
||||
if (cJSON_IsArray(tunnel_array)) {
|
||||
const cJSON* item = nullptr;
|
||||
cJSON_ArrayForEach(item, tunnel_array) {
|
||||
if (cJSON_IsString(item) && item->valuestring != nullptr) {
|
||||
tunnel_keys.emplace_back(item->valuestring);
|
||||
}
|
||||
}
|
||||
} else if (cJSON_IsString(tunnel_array) && tunnel_array->valuestring != nullptr) {
|
||||
tunnel_keys.emplace_back(tunnel_array->valuestring);
|
||||
}
|
||||
if (!confirmed || backbone_key == nullptr) {
|
||||
cJSON_Delete(body_root);
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG,
|
||||
"OAM keyring upload confirmation and backboneKeyHex are required");
|
||||
}
|
||||
const std::string backbone_key_value(backbone_key);
|
||||
const std::string device_auth_key_value(device_auth_key == nullptr ? "" : device_auth_key);
|
||||
cJSON_Delete(body_root);
|
||||
if (!openknx::WriteOamIpSecureKeyringHex(backbone_key_value, tunnel_keys,
|
||||
device_auth_key_value, activated)) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG,
|
||||
"invalid OAM IP Secure keyring material");
|
||||
}
|
||||
if (runtime->knx_router != nullptr) {
|
||||
runtime->knx_router->setOamIpSecureCredentials(
|
||||
openknx::LoadOamIpSecureCredentialMaterial());
|
||||
}
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate OAM keyring response");
|
||||
}
|
||||
cJSON* credentials_json = IpSecureCredentialStatusToCjson(
|
||||
openknx::LoadOamIpSecureCredentialStatus());
|
||||
if (credentials_json != nullptr) {
|
||||
cJSON_AddItemToObject(response, "ipSecureCredentials", credentials_json);
|
||||
}
|
||||
return JsonOk(response);
|
||||
#else
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||
"KNXnet/IP Secure development endpoints are disabled");
|
||||
#endif
|
||||
}
|
||||
if (action == "knx_oam_security_clear_keyring") {
|
||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
|
||||
defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
|
||||
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||
if (body_root == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
|
||||
}
|
||||
const char* confirm = JsonString(body_root, "confirm");
|
||||
const bool confirmed = confirm != nullptr &&
|
||||
std::string_view(confirm) == "clear-oam-ip-secure-keyring";
|
||||
cJSON_Delete(body_root);
|
||||
if (!confirmed) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG,
|
||||
"OAM keyring clear confirmation is required");
|
||||
}
|
||||
if (!openknx::ClearOamIpSecureKeyring()) {
|
||||
return ErrorResponse(ESP_FAIL, "failed to clear OAM IP Secure keyring");
|
||||
}
|
||||
if (runtime->knx_router != nullptr) {
|
||||
runtime->knx_router->setOamIpSecureCredentials(
|
||||
openknx::LoadOamIpSecureCredentialMaterial());
|
||||
}
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response == nullptr) {
|
||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate OAM keyring response");
|
||||
}
|
||||
cJSON* credentials_json = IpSecureCredentialStatusToCjson(
|
||||
openknx::LoadOamIpSecureCredentialStatus());
|
||||
if (credentials_json != nullptr) {
|
||||
cJSON_AddItemToObject(response, "ipSecureCredentials", credentials_json);
|
||||
}
|
||||
return JsonOk(response);
|
||||
#else
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||
"KNXnet/IP Secure development endpoints are disabled");
|
||||
#endif
|
||||
}
|
||||
if (action == "bacnet_start") {
|
||||
|
||||
@@ -22,13 +22,21 @@ 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,
|
||||
@@ -36,6 +44,37 @@ constexpr uint8_t kCrc4Tab[16] = {
|
||||
constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
constexpr char kHexAlphabet[] = "0123456789ABCDEF";
|
||||
|
||||
struct FactoryFdskContext {
|
||||
const char* nvsNamespace;
|
||||
const char* productIdentity;
|
||||
const char* derivationLabel;
|
||||
uint16_t manufacturerId;
|
||||
uint16_t applicationNumber;
|
||||
uint8_t applicationVersion;
|
||||
uint32_t serialMacIncrement;
|
||||
bool clearOpenKnxCache;
|
||||
};
|
||||
|
||||
constexpr FactoryFdskContext kReg1Context{
|
||||
kNamespace,
|
||||
kProductIdentity,
|
||||
kFdskDerivationLabel,
|
||||
gateway::knx_internal::kReg1DaliManufacturerId,
|
||||
gateway::knx_internal::kReg1DaliApplicationNumber,
|
||||
gateway::knx_internal::kReg1DaliApplicationVersion,
|
||||
gateway::knx_internal::kReg1DaliSerialMacIncrement,
|
||||
true};
|
||||
|
||||
constexpr 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));
|
||||
|
||||
std::string hexValue(uint32_t value, int width) {
|
||||
@@ -120,7 +159,7 @@ bool parseHexKey(const std::string& value, uint8_t* out) {
|
||||
return plausibleKey(out);
|
||||
}
|
||||
|
||||
bool loadKnxSerialNumber(uint8_t* serial) {
|
||||
bool loadKnxSerialNumber(const FactoryFdskContext& context, uint8_t* serial) {
|
||||
if (serial == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -129,22 +168,30 @@ bool loadKnxSerialNumber(uint8_t* serial) {
|
||||
return false;
|
||||
}
|
||||
|
||||
serial[0] = static_cast<uint8_t>(
|
||||
(gateway::knx_internal::kReg1DaliManufacturerId >> 8) & 0xff);
|
||||
serial[1] = static_cast<uint8_t>(
|
||||
gateway::knx_internal::kReg1DaliManufacturerId & 0xff);
|
||||
std::copy(mac.begin() + 2, mac.end(), serial + 2);
|
||||
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 uint8_t* serial, uint8_t* key) {
|
||||
bool deriveFactoryFdskFromSerial(const FactoryFdskContext& context,
|
||||
const uint8_t* serial, uint8_t* key) {
|
||||
if (serial == nullptr || key == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, sizeof(kFdskDerivationLabel) - 1 + kSerialSize> material{};
|
||||
std::copy(kFdskDerivationLabel, kFdskDerivationLabel + sizeof(kFdskDerivationLabel) - 1,
|
||||
material.begin());
|
||||
std::copy(serial, serial + kSerialSize, material.begin() + sizeof(kFdskDerivationLabel) - 1);
|
||||
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) {
|
||||
@@ -157,7 +204,7 @@ bool deriveFactoryFdskFromSerial(const uint8_t* serial, uint8_t* key) {
|
||||
return plausibleKey(key);
|
||||
}
|
||||
|
||||
void syncFactoryFdskToNvs(const uint8_t* data) {
|
||||
void syncFactoryFdskToNvs(const FactoryFdskContext& context, const uint8_t* data) {
|
||||
if (data == nullptr || !plausibleKey(data) || !ensureNvsReady()) {
|
||||
return;
|
||||
}
|
||||
@@ -166,7 +213,7 @@ void syncFactoryFdskToNvs(const uint8_t* data) {
|
||||
size_t stored_size = stored.size();
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t err = nvs_open(kNamespace, NVS_READWRITE, &handle);
|
||||
esp_err_t err = nvs_open(context.nvsNamespace, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
|
||||
return;
|
||||
@@ -186,7 +233,9 @@ void syncFactoryFdskToNvs(const uint8_t* data) {
|
||||
ESP_LOGW(kTag, "failed to mirror deterministic KNX factory FDSK: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
clearOpenKnxFdskCache();
|
||||
if (context.clearOpenKnxCache) {
|
||||
clearOpenKnxFdskCache();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t crc4Array(const uint8_t* data, size_t len) {
|
||||
@@ -275,27 +324,28 @@ std::string fnv1aHex(const std::string& value) {
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
bool LoadFactoryFdsk(uint8_t* data, size_t len) {
|
||||
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(serial.data()) ||
|
||||
!deriveFactoryFdskFromSerial(serial.data(), key.data())) {
|
||||
if (!loadKnxSerialNumber(context, serial.data()) ||
|
||||
!deriveFactoryFdskFromSerial(context, serial.data(), key.data())) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(data, key.data(), kFdskSize);
|
||||
syncFactoryFdskToNvs(key.data());
|
||||
syncFactoryFdskToNvs(context, key.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
FactoryFdskInfo LoadFactoryFdskInfo() {
|
||||
FactoryFdskInfo LoadFactoryFdskInfoForContext(const FactoryFdskContext& context) {
|
||||
FactoryFdskInfo info;
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
std::array<uint8_t, kSerialSize> serial{};
|
||||
if (!loadKnxSerialNumber(serial.data()) || !LoadFactoryFdsk(key.data(), key.size())) {
|
||||
if (!loadKnxSerialNumber(context, serial.data()) ||
|
||||
!LoadFactoryFdskForContext(context, key.data(), key.size())) {
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -306,31 +356,34 @@ FactoryFdskInfo LoadFactoryFdskInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
|
||||
bool GenerateFactoryFdskForContext(const FactoryFdskContext& context,
|
||||
FactoryFdskInfo* info) {
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
const bool stored = LoadFactoryFdsk(key.data(), key.size());
|
||||
const bool stored = LoadFactoryFdskForContext(context, key.data(), key.size());
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
if (info != nullptr) {
|
||||
*info = LoadFactoryFdskInfo();
|
||||
*info = LoadFactoryFdskInfoForContext(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
|
||||
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(serial.data()) &&
|
||||
deriveFactoryFdskFromSerial(serial.data(), derived.data()) &&
|
||||
const bool stored = loadKnxSerialNumber(context, serial.data()) &&
|
||||
deriveFactoryFdskFromSerial(context, serial.data(), derived.data()) &&
|
||||
std::equal(key.begin(), key.end(), derived.begin());
|
||||
if (stored) {
|
||||
syncFactoryFdskToNvs(derived.data());
|
||||
syncFactoryFdskToNvs(context, derived.data());
|
||||
}
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
std::fill(derived.begin(), derived.end(), 0);
|
||||
@@ -338,33 +391,36 @@ bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
|
||||
return false;
|
||||
}
|
||||
if (info != nullptr) {
|
||||
*info = LoadFactoryFdskInfo();
|
||||
*info = LoadFactoryFdskInfoForContext(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResetFactoryFdskCache(FactoryFdskInfo* info) {
|
||||
clearOpenKnxFdskCache();
|
||||
const auto loaded = LoadFactoryFdskInfo();
|
||||
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 BuildFactoryCertificatePayload() {
|
||||
FactoryCertificatePayload BuildFactoryCertificatePayloadForContext(
|
||||
const FactoryFdskContext& context) {
|
||||
FactoryCertificatePayload payload;
|
||||
const auto info = LoadFactoryFdskInfo();
|
||||
const auto info = LoadFactoryFdskInfoForContext(context);
|
||||
if (!info.available) {
|
||||
return payload;
|
||||
}
|
||||
payload.available = true;
|
||||
payload.productIdentity = kProductIdentity;
|
||||
payload.manufacturerId = hexValue(gateway::knx_internal::kReg1DaliManufacturerId, 4);
|
||||
payload.applicationNumber = hexValue(
|
||||
gateway::knx_internal::kReg1DaliApplicationNumber, 2);
|
||||
payload.applicationVersion = hexValue(
|
||||
gateway::knx_internal::kReg1DaliApplicationVersion, 2);
|
||||
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;
|
||||
@@ -377,6 +433,241 @@ FactoryCertificatePayload BuildFactoryCertificatePayload() {
|
||||
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 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);
|
||||
}
|
||||
|
||||
FactoryCertificatePayload BuildFactoryCertificatePayload() {
|
||||
return BuildFactoryCertificatePayloadForContext(kReg1Context);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user