diff --git a/src/Makefile b/src/Makefile index 86b626e99f7df0678da0f39e21fc883b07fbc1b5..9ed290e4348026a9c2d7efd18115911226a6f97d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,6 +18,7 @@ endif OBJS = \ utils/debug.o \ + utils/libcrypto.o \ utils/liblist.o \ utils/utils.o \ utils/1905_ubus.o \ @@ -38,6 +39,7 @@ AGENT_OBJS = \ agent_tlv.o \ agent_cmdu.o \ assoc_ctrl.o \ + autoconfig.o \ backhaul.o \ backhaul_blacklist.o \ config.o \ diff --git a/src/agent.c b/src/agent.c index 7568bf8da17f64d74d6ae9f147713a8262160308..5c93d7b785dbc57ea6ef9ee70ebcce2501830ed7 100644 --- a/src/agent.c +++ b/src/agent.c @@ -48,6 +48,7 @@ #include "agent_ubus.h" #include "agent_ubus_dbg.h" #include "assoc_ctrl.h" +#include "autoconfig.h" #include "backhaul.h" #include "backhaul_blacklist.h" #include "config.h" @@ -5774,6 +5775,7 @@ struct wifi_radio_element *agent_add_radio(struct agent *a, char *name) timer_init(&re->bk.steer_timeout, bsta_steer_cb); timer_init(&re->unassoc_sta_meas_timer, agent_unassoc_sta_meas_timer_cb); + return re; } @@ -5784,6 +5786,7 @@ static int init_wifi_radios(struct agent *a) int ret = 0; list_for_each_entry(rcfg, &a->cfg.radiolist, list) { + uint8_t zero_block[SHA256_LENGTH] = {0}; wifi_object_t r_wobj = WIFI_OBJECT_INVALID; struct wifi_radio_element *re; char r_objname[32] = {0}; @@ -5803,6 +5806,11 @@ static int init_wifi_radios(struct agent *a) dbg("%s: getting radio:%s status\n", __func__, re->name); ret = ubus_call_object(a, r_wobj, "status", parse_radio, re); + if (!ret && !memcmp(zero_block, re->autconfig.sha256, SHA256_LENGTH)) { + if (autoconfig_radio_hash_from_file(re->macaddr, re->autconfig.sha256)) + dbg("%s: Autoconfig SHA256 for radio %s is unset\n", __func__, re->name); + } + /* Get fresh opclass preferences after scan */ wifi_radio_update_opclass_preferences(a, re, 1); agent_set_post_scan_action_pref(a, re, true); @@ -8108,6 +8116,11 @@ void run_agent(void *opts) agent_get_system_info(w); +#if (EASYMESH_VERSION >= 6) + autoconfig_ap_mld_hash_from_file(w->ap_mld_cfg_sha256); + autoconfig_bsta_mld_hash_from_file(w->bsta_mld_cfg_sha256); +#endif + /* w->cfg.enabled */ agent_publish_object(w, MAPAGENT_OBJECT); agent_publish_dbg_object(w, MAPAGENT_DBG_OBJECT); diff --git a/src/agent.h b/src/agent.h index 3c70685d4cb00c28edf477c6fe7fa14549e01af2..c66ac62b84d7b111ac9d1ca190c78db4e80e36fb 100644 --- a/src/agent.h +++ b/src/agent.h @@ -26,6 +26,8 @@ #include "utils/allmac.h" #include "utils/debug.h" +#include "utils/libcrypto.h" +#include "autoconfig.h" #include "config.h" #include "nl.h" #include "wifi.h" @@ -606,18 +608,6 @@ struct wsc_ext { struct wsc_vendor_ie ven_ies[VEN_IES_MAX]; }; -struct wsc_data { - uint8_t *m1_frame; - uint16_t m1_size; - struct wsc_key *key; -}; - -#define HEARTBEAT_AUTOCFG_INTERVAL 70 -enum autocfg_state { - AUTOCFG_HEARTBEAT, - AUTOCFG_ACTIVE -}; - #define SCAN_MAX_CHANNEL 16 struct wifi_scan_request_opclass { uint8_t classid; @@ -686,7 +676,6 @@ struct wifi7_radio_capabilities { }; #endif -#define SHA256_LENGTH 32 struct wifi_radio_element { char name[16]; uint8_t macaddr[6]; @@ -754,7 +743,6 @@ struct wifi_radio_element { uint16_t mid; uint16_t wsc_mid; uint16_t renew_mid; /* debug purposes */ - uint8_t autconfig_wsc_sha256[SHA256_LENGTH]; /* radio scan state */ enum wifi_scan_state scan_state; diff --git a/src/agent_map.c b/src/agent_map.c index 8d0ebc228242bf3ac768ba5453aefdc0dc09cffd..4886ca4bf62f6d9d6113eb81ff83fc42c3b20923 100644 --- a/src/agent_map.c +++ b/src/agent_map.c @@ -48,7 +48,7 @@ #include "agent_tlv.h" #include "agent_ubus.h" #include "assoc_ctrl.h" -#include "utils/allmac.h" +#include "autoconfig.h" #include "backhaul.h" #include "backhaul_blacklist.h" #include "config.h" @@ -62,9 +62,13 @@ #include "extension.h" #include "timer_impl.h" #include "unasta.h" -#include "utils/1905_ubus.h" + +#include "utils/allmac.h" #include "utils/debug.h" +#include "utils/1905_ubus.h" +#include "utils/libcrypto.h" #include "utils/utils.h" + #include "wifi.h" #include "wifi_opclass.h" #include "wifi_scanresults.h" @@ -2041,146 +2045,6 @@ bool agent_is_ts_enabled(struct agent *a) return true; } -static bool has_ap_autoconfig_wsc_changed(struct agent *a, - struct tlv *tlvs[][TLV_MAXNUM], - size_t tlvs_size, uint8_t *sha256_out) -{ - enum { - AP_RADIO_ID = 0, - WSC = 1, - DEFAULT_8021Q_SETTINGS = 2, - TRAFFIC_SEPARATION_POLICY = 3, -#if (EASYMESH_VERSION >= 6) - AP_MLD_CONFIG = 4, - BACKHAUL_STA_MLD_CONFIG = 5, - MAX_TLV_TYPES = 6 -#else - MAX_TLV_TYPES = 4 -#endif - }; - - uint8_t new_sha256[SHA256_LENGTH]; - const struct wifi_radio_element *radio; - uint8_t bssid[6]; - EVP_MD_CTX *ctx; - int i; - bool ret = true; - - if (tlvs_size != MAX_TLV_TYPES) { - err("%s: Unsupported version of CMDU.\n", __func__); - return true; - } - - memcpy(bssid, tlvs[AP_RADIO_ID][0]->data, 6); - radio = agent_get_radio(a, bssid); - if (!radio) { - err("%s: Unknown radio.\n", __func__); - return true; - } - - /* Calculate SHA-256 hash from all TLVs data */ - ctx = EVP_MD_CTX_new(); - if (!ctx) - return true; - - if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) - goto error_cleanup; - - if (!EVP_DigestUpdate(ctx, tlvs[AP_RADIO_ID][0]->data, tlv_length(tlvs[AP_RADIO_ID][0]))) - goto error_cleanup; - - /* Use decrypted data of WSC TLVs */ - i = 0; - while (i < TLV_MAXNUM && tlvs[WSC][i]) { - struct wps_credential wps_out = { 0 }; - uint8_t *ext_out = NULL; - uint16_t ext_len = 0; - int ret = 0; - uint16_t m2_len = tlv_length(tlvs[WSC][i]); - /* It's workaround as wsc_process_m2 modifies m2 buffer */ - uint8_t *m2_tmp = malloc(m2_len); - - if (!m2_tmp) - goto error_cleanup; - - memcpy(m2_tmp, tlvs[WSC][i]->data, m2_len); - - ret = wsc_process_m2(radio->autconfig.m1_frame, - radio->autconfig.m1_size, - radio->autconfig.key, m2_tmp, m2_len, - &wps_out, &ext_out, &ext_len); - - free(m2_tmp); - - if (ret) { - ret = false; - goto error_cleanup; - } - - if (!EVP_DigestUpdate(ctx, &wps_out, sizeof(wps_out))) { - free(ext_out); - goto error_cleanup; - } - - if (ext_out && !EVP_DigestUpdate(ctx, ext_out, ext_len)) { - free(ext_out); - goto error_cleanup; - } - - free(ext_out); - - ++i; - } - - if (tlvs[DEFAULT_8021Q_SETTINGS][0] && - !EVP_DigestUpdate(ctx, tlvs[DEFAULT_8021Q_SETTINGS][0]->data, - tlv_length(tlvs[DEFAULT_8021Q_SETTINGS][0]))) { - goto error_cleanup; - } - - if (tlvs[TRAFFIC_SEPARATION_POLICY][0] && - !EVP_DigestUpdate(ctx, tlvs[TRAFFIC_SEPARATION_POLICY][0]->data, - tlv_length(tlvs[TRAFFIC_SEPARATION_POLICY][0]))) { - goto error_cleanup; - } - -#if (EASYMESH_VERSION >= 6) - if (tlvs[AP_MLD_CONFIG][0] && - !EVP_DigestUpdate(ctx, tlvs[AP_MLD_CONFIG][0]->data, - tlv_length(tlvs[AP_MLD_CONFIG][0]))) { - goto error_cleanup; - } - - if (tlvs[BACKHAUL_STA_MLD_CONFIG][0] && - !EVP_DigestUpdate(ctx, tlvs[BACKHAUL_STA_MLD_CONFIG][0]->data, - tlv_length(tlvs[BACKHAUL_STA_MLD_CONFIG][0]))) { - goto error_cleanup; - } -#endif - - if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL)) - goto error_cleanup; - - EVP_MD_CTX_free(ctx); - - dbg("%s: current SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__, - radio->autconfig_wsc_sha256[0], radio->autconfig_wsc_sha256[1], - radio->autconfig_wsc_sha256[2], radio->autconfig_wsc_sha256[3], - radio->autconfig_wsc_sha256[4], radio->autconfig_wsc_sha256[5]); - - dbg("%s: new SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__, - new_sha256[0], new_sha256[1], new_sha256[2], new_sha256[3], - new_sha256[4], new_sha256[5]); - - memcpy(sha256_out, new_sha256, SHA256_LENGTH); - - return memcmp(radio->autconfig_wsc_sha256, new_sha256, SHA256_LENGTH); - -error_cleanup: - EVP_MD_CTX_free(ctx); - return ret; -} - #define RELOAD_TIMEOUT 5 #if (EASYMESH_VERSION >= 6) static void mlo_update_id_in_configs(char *ifname, uint8_t mld_id) @@ -2519,37 +2383,6 @@ static int mlo_process_bsta_mld_config(struct agent *a, uint8_t *tlv_data) } #endif -#if (EASYMESH_VERSION >= 6) -static bool update_tlv_hash(struct agent *a, struct tlv *tv, - uint8_t *sha256_out) -{ - bool ret = false; - uint8_t new_sha256[SHA256_LENGTH] = {0}; - EVP_MD_CTX *ctx; - - ctx = EVP_MD_CTX_new(); - if (!ctx) - return true; - - if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) - goto error_cleanup; - - if (!EVP_DigestUpdate(ctx, tv->data, tlv_length(tv))) - goto error_cleanup; - - if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL)) - goto error_cleanup; - - if (memcmp(sha256_out, new_sha256, SHA256_LENGTH)) { - memcpy(sha256_out, new_sha256, SHA256_LENGTH); - ret = true; - } - -error_cleanup: - EVP_MD_CTX_free(ctx); - return ret; -} -#endif int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, struct node *n) { @@ -2563,6 +2396,7 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, #ifdef CHECK_PARTIAL_WIFI_RELOAD bool full_reconf_required = false; #endif /* CHECK_PARTIAL_WIFI_RELOAD */ + int diff = DIFF_NONE; trace("%s: --->\n", __func__); @@ -2593,8 +2427,11 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, dbg("|%s:%d| found radio = %s\n", __func__, __LINE__, radio->name); - if (!has_ap_autoconfig_wsc_changed(a, tv, - AP_AUTOCONFIGURATION_WSC_M2_NUM_OF_TLV_TYPES, new_sha256)) { + diff = autoconfig_wsc_m2_diff(radio, tv, + AP_AUTOCONFIGURATION_WSC_M2_NUM_OF_TLV_TYPES, + new_sha256); + + if (diff == DIFF_NONE || diff == DIFF_ERR) { info("%s: The same config received, skip radio configuration for: " MACFMT "\n", __func__, MAC2STR(radio->macaddr)); @@ -2650,7 +2487,7 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, */ agent_autoconfig_event(a, radio->name, "teardown", "M2 process failure"); - memset(radio->autconfig_wsc_sha256, 0, SHA256_LENGTH); + autoconfig_clean_wsc_hash(&radio->autconfig); return -1; } @@ -2668,7 +2505,7 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, "IOPSYS extension process error"); if (ext) free(ext); - memset(radio->autconfig_wsc_sha256, 0, SHA256_LENGTH); + autoconfig_clean_wsc_hash(&radio->autconfig); return -1; } @@ -2797,10 +2634,11 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, if (tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0]) { mlo_process_ap_mld_config(a, tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0]->data); - update_tlv_hash(a, tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0], - a->ap_mld_cfg_sha256); a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID; timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000); + + if (diff & DIFF_AP_MLD) + autoconfig_ap_mld_hash_to_file(a->ap_mld_cfg_sha256); } if (tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0]) { @@ -2810,15 +2648,18 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0]->data); if (ret) wifi_teardown_map_bsta_mld(a, radio->band); - update_tlv_hash(a, tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0], - a->bsta_mld_cfg_sha256); a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID; timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000); + + if (diff & DIFF_BSTA_MLD) + autoconfig_bsta_mld_hash_to_file(a->bsta_mld_cfg_sha256); } else { dbg("%s: Missing BSTA MLD config TLV, teardown\n", __func__); wifi_teardown_map_bsta_mld(a, radio->band); } + + #endif dbg("|%s:%d| radio (%s) was configured! Apply heartbeat for this radio\n", @@ -2827,7 +2668,9 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu, agent_autoconfig_event(a, radio->name, "success", "completed"); /* Save successfully processed autoconfig wsc SHA256 */ - memcpy(radio->autconfig_wsc_sha256, new_sha256, SHA256_LENGTH); + autoconfig_update_wsc_hash(&radio->autconfig, new_sha256); + autoconfig_radio_hash_to_file(radio->macaddr, new_sha256); + dbg("%s: Applied autoconfig SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__, new_sha256[0], new_sha256[1], new_sha256[2], new_sha256[3], new_sha256[4], new_sha256[5]); @@ -6706,7 +6549,8 @@ int handle_ap_mld_config_request(void *agent, struct cmdu_buff *cmdu, struct nod /* Send the 1905 ack message to Controller */ send_1905_acknowledge(a, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0); - if (update_tlv_hash(a, tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0], + if (update_sha256_hash(tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]->data, + tlv_length(tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]), a->ap_mld_cfg_sha256)) { /* Proces ReConfiguration */ mlo_process_ap_mld_config(a, tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]->data); @@ -6772,7 +6616,8 @@ int handle_bsta_mld_config_request(void *agent, struct cmdu_buff *cmdu, struct n send_1905_acknowledge(a, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0); /* Process TLV only if changed */ - if (update_tlv_hash(a, tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0], + if (update_sha256_hash(tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0]->data, + tlv_length(tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0]), a->bsta_mld_cfg_sha256)) { int ret = 0; diff --git a/src/autoconfig.c b/src/autoconfig.c new file mode 100644 index 0000000000000000000000000000000000000000..38a422913a2c2f059a9821f9b98e8e563b76cda3 --- /dev/null +++ b/src/autoconfig.c @@ -0,0 +1,572 @@ +/* + * autoconfig.c - implements autoconfig logic and util functions + * + * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved. + * + */ +#include <json-c/json.h> +#include <libubox/blobmsg_json.h> + +#include <stdlib.h> +#include <string.h> + +#include <1905_tlvs.h> +#include <i1905_wsc.h> +#include <cmdu.h> +#include <openssl/evp.h> + +#include "agent.h" +#include "autoconfig.h" + +#include "utils/debug.h" + +void autoconfig_clean_wsc_hash(struct wsc_data *data) +{ + memset(data->sha256, 0, SHA256_LENGTH); +} + +void autoconfig_update_wsc_hash(struct wsc_data *data, uint8_t *new_sha256) +{ + memcpy(&data->sha256, new_sha256, SHA256_LENGTH); +} + +int autoconfig_wsc_m2_diff(struct wifi_radio_element *re, + struct tlv *tlvs[][TLV_MAXNUM], + size_t tlvs_size, + uint8_t *wsc_m2_sha256) +{ + enum { + AP_RADIO_ID = 0, + WSC = 1, + DEFAULT_8021Q_SETTINGS = 2, + TRAFFIC_SEPARATION_POLICY = 3, +#if (EASYMESH_VERSION >= 6) + AP_MLD_CONFIG = 4, + BACKHAUL_STA_MLD_CONFIG = 5, + MAX_TLV_TYPES = 6 +#else + MAX_TLV_TYPES = 4 +#endif + }; + + uint8_t new_sha256[SHA256_LENGTH]; + uint8_t bssid[6]; + EVP_MD_CTX *ctx; + int i; + int diff = DIFF_NONE; + struct wsc_data *data = &re->autconfig; + + if (tlvs_size != MAX_TLV_TYPES) { + agnt_dbg(LOG_APCFG, "%s: Unsupported version of CMDU.\n", __func__); + return DIFF_ERR; + } + + memcpy(bssid, tlvs[AP_RADIO_ID][0]->data, 6); + + /* Calculate SHA-256 hash from all TLVs data */ + ctx = EVP_MD_CTX_new(); + if (!ctx) + return DIFF_ERR; + + if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) + goto error_cleanup; + + if (!EVP_DigestUpdate(ctx, tlvs[AP_RADIO_ID][0]->data, tlv_length(tlvs[AP_RADIO_ID][0]))) + goto error_cleanup; + + /* Use decrypted data of WSC TLVs */ + i = 0; + while (i < TLV_MAXNUM && tlvs[WSC][i]) { + struct wps_credential wps_out = { 0 }; + uint8_t *ext_out = NULL; + uint16_t ext_len = 0; + uint16_t m2_len = tlv_length(tlvs[WSC][i]); + /* It's workaround as wsc_process_m2 modifies m2 buffer */ + uint8_t *m2_tmp = malloc(m2_len); + int ret; + + if (!m2_tmp) + goto error_cleanup; + + memcpy(m2_tmp, tlvs[WSC][i]->data, m2_len); + + ret = wsc_process_m2(data->m1_frame, + data->m1_size, + data->key, m2_tmp, m2_len, + &wps_out, &ext_out, &ext_len); + + free(m2_tmp); + + if (ret) + goto error_cleanup; + + if (!EVP_DigestUpdate(ctx, &wps_out, sizeof(wps_out))) { + free(ext_out); + goto error_cleanup; + } + + if (ext_out && !EVP_DigestUpdate(ctx, ext_out, ext_len)) { + free(ext_out); + goto error_cleanup; + } + + free(ext_out); + + ++i; + } + + if (tlvs[DEFAULT_8021Q_SETTINGS][0] && + !EVP_DigestUpdate(ctx, tlvs[DEFAULT_8021Q_SETTINGS][0]->data, + tlv_length(tlvs[DEFAULT_8021Q_SETTINGS][0]))) { + goto error_cleanup; + } + + if (tlvs[TRAFFIC_SEPARATION_POLICY][0] && + !EVP_DigestUpdate(ctx, tlvs[TRAFFIC_SEPARATION_POLICY][0]->data, + tlv_length(tlvs[TRAFFIC_SEPARATION_POLICY][0]))) { + goto error_cleanup; + } + + if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL)) + goto error_cleanup; + + EVP_MD_CTX_free(ctx); + + agnt_dbg(LOG_APCFG, "%s: current WSC M2 SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__, + data->sha256[0], data->sha256[1], + data->sha256[2], data->sha256[3], + data->sha256[4], data->sha256[5]); + + agnt_dbg(LOG_APCFG, "%s: new WSC M2 SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__, + new_sha256[0], new_sha256[1], + new_sha256[2], new_sha256[3], + new_sha256[4], new_sha256[5]); + + memcpy(wsc_m2_sha256, new_sha256, SHA256_LENGTH); + if (memcmp(data->sha256, new_sha256, SHA256_LENGTH)) + diff |= DIFF_WSC_M2; + +#if (EASYMESH_VERSION >= 6) + if (tlvs[AP_MLD_CONFIG][0]) { + bool ap_mld_updated = false; + + ap_mld_updated = update_sha256_hash(tlvs[AP_MLD_CONFIG][0]->data, + tlv_length(tlvs[AP_MLD_CONFIG][0]), + re->agent->ap_mld_cfg_sha256); + + if (ap_mld_updated) { + agnt_dbg(LOG_APCFG, "%s: AP MLD SHA256 has changed\n", __func__); + diff |= DIFF_AP_MLD; + } else + agnt_dbg(LOG_APCFG, "%s: no change of AP MLD SHA256\n", __func__); + } + + if (tlvs[BACKHAUL_STA_MLD_CONFIG][0]) { + bool bsta_mld_updated = false; + + bsta_mld_updated = update_sha256_hash(tlvs[BACKHAUL_STA_MLD_CONFIG][0]->data, + tlv_length(tlvs[BACKHAUL_STA_MLD_CONFIG][0]), + re->agent->bsta_mld_cfg_sha256); + if (bsta_mld_updated) { + agnt_dbg(LOG_APCFG, "%s: BSTA MLD SHA256 has changed\n", __func__); + diff |= DIFF_BSTA_MLD; + } else + agnt_dbg(LOG_APCFG, "%s: no change of BSTA MLD SHA256\n", __func__); + } +#endif + return diff; + +error_cleanup: + EVP_MD_CTX_free(ctx); + + return DIFF_ERR; +} + +static const char *json_get_string(struct json_object *object, const char *key) +{ + json_bool ret; + struct json_object *value; + + if (!object || !json_object_is_type(object, json_type_object)) + return NULL; + + ret = json_object_object_get_ex(object, key, &value); + if (!ret || !value || !json_object_is_type(value, json_type_string)) + return NULL; + + return json_object_get_string(value); +} + +#if (EASYMESH_VERSION >= 6) +enum autocfg_mld { + AP_MLD, + BSTA_MLD, +}; + +static int autoconfig_mld_hash_to_file(uint8_t *sha256, int type) +{ + struct json_object *wsc_json; + struct json_object *sha256_obj; + char type_str[16] = {}; + char *sha256_str; + json_bool found_in_file; + + dbg("%s: file:%s\n", __func__, AUTOCFG_FILE); + + strncpy(type_str, + ((type == AP_MLD) ? "ap_mld_sha256" : "bsta_mld_sha256"), + sizeof(type_str) - 1); + + /* Get the json object from the file */ + wsc_json = json_object_from_file(AUTOCFG_FILE); + if (!wsc_json) { + agnt_dbg(LOG_APCFG, "%s: failed to read json:%s, error:%s. "\ + "Try to generate new\n", __func__, + AUTOCFG_FILE, json_util_get_last_err()); + + wsc_json = json_object_new_object(); + if (!wsc_json) { + agnt_warn(LOG_APCFG, "%s: failed to create json obj, error:%s. ", + __func__, json_util_get_last_err()); + return -1; + } + } + + found_in_file = json_object_object_get_ex(wsc_json, type_str, &sha256_obj); + if (found_in_file) { + /* Remove old SHA first */ + json_object_object_del(wsc_json, type_str); + } + + /* Create sha256 string */ + sha256_str = calloc(SHA256_LENGTH * 2 + 1, sizeof(char)); + if (!sha256_str) + goto out; + btostr(sha256, SHA256_LENGTH, sha256_str); + + sha256_obj = json_object_new_string(sha256_str); + if (WARN_ON(!sha256_obj)) { + agnt_warn(LOG_APCFG, + "%s: failed to add %s, error:%s. ", + __func__, type_str, json_util_get_last_err()); + goto free; + } + + json_object_object_add(wsc_json, type_str, sha256_obj); + + /* Update sha256 file */ + json_object_to_file(AUTOCFG_FILE, wsc_json); + + agnt_dbg(LOG_APCFG, "%s: Wrote %s SHA256: %02x%02x%02x%02x%02x%02x... to file %s\n", + __func__, ((type == AP_MLD) ? "AP MLD" : "BSTA MLD"), + sha256[0], sha256[1], + sha256[2], sha256[3], + sha256[4], sha256[5], + AUTOCFG_FILE); + +free: + free(sha256_str); +out: + json_object_put(wsc_json); + + return 0; +} + +static int get_mld_hash_from_file(uint8_t *sha256_out, int type, const struct blobmsg_policy *attr) +{ + struct blob_attr *tb[1]; + struct blob_buf wsc_m2_file = {0}; + char *sha256_str; + int blen; + int ret = 0; + + blob_buf_init(&wsc_m2_file, 0); + + if (!blobmsg_add_json_from_file(&wsc_m2_file, AUTOCFG_FILE)) { + agnt_dbg(LOG_APCFG, "Failed to parse %s\n", AUTOCFG_FILE); + ret = -1; + goto free; + } + + blobmsg_parse(attr, 1, tb, blob_data(wsc_m2_file.head), blob_len(wsc_m2_file.head)); + + if (!tb[0]) { + ret = -1; + goto free; + } + + sha256_str = blobmsg_get_string(tb[0]); + if (!sha256_str) { + agnt_err(LOG_APCFG, "%s: No valid %s SHA256\n", __func__, + ((type == AP_MLD) ? "AP MLD" : "BSTA MLD")); + ret = -1; + goto free; + } + + blen = strlen(sha256_str) / 2; + if (blen > SHA256_LENGTH) { + agnt_err(LOG_APCFG, "%s: %s SHA256 too long\n", __func__, + ((type == AP_MLD) ? "AP MLD" : "BSTA MLD")); + ret = -1; + goto free; + } + + strtob(sha256_str, blen, sha256_out); + + dbg("%s: Read %s SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__, + ((type == AP_MLD) ? "AP MLD" : "BSTA MLD"), + sha256_out[0], sha256_out[1], + sha256_out[2], sha256_out[3], + sha256_out[4], sha256_out[5]); + +free: + blob_buf_free(&wsc_m2_file); + + return ret; +} + +/* keep the AP MLD md5sum in non-persistent storage */ +int autoconfig_ap_mld_hash_to_file(uint8_t *sha256) +{ + return autoconfig_mld_hash_to_file(sha256, AP_MLD); +} + +int autoconfig_ap_mld_hash_from_file(uint8_t *sha256_out) +{ + static const struct blobmsg_policy attr_ap[] = { + [0] = { .name = "ap_mld_sha256", .type = BLOBMSG_TYPE_STRING }, + }; + + return get_mld_hash_from_file(sha256_out, AP_MLD, attr_ap); +} + +/* keep the BSTA MLD md5sum in non-persistent storage */ +int autoconfig_bsta_mld_hash_to_file(uint8_t *sha256) +{ + return autoconfig_mld_hash_to_file(sha256, BSTA_MLD); +} + +int autoconfig_bsta_mld_hash_from_file(uint8_t *sha256_out) +{ + static const struct blobmsg_policy attr_bsta[] = { + [0] = { .name = "bsta_mld_sha256", .type = BLOBMSG_TYPE_STRING }, + }; + + return get_mld_hash_from_file(sha256_out, BSTA_MLD, attr_bsta); +} +#endif /* EASYMESH_VERSION >= 6 */ + +/* keep the WSC M2 md5sum in non-persistent storage */ +int autoconfig_radio_hash_to_file(uint8_t *macaddr, uint8_t *sha256) +{ + struct json_object *wsc_json; + struct json_object *radios, *radio_obj = NULL; + struct json_object *sha256_obj; + char *sha256_str; + json_bool ret; + int len, i; + + dbg("%s: file:%s\n", __func__, AUTOCFG_FILE); + + /* Get the json object from the file */ + wsc_json = json_object_from_file(AUTOCFG_FILE); + if (!wsc_json) { + agnt_dbg(LOG_APCFG, "%s: failed to read json:%s, error:%s. "\ + "Try to generate new\n", __func__, + AUTOCFG_FILE, json_util_get_last_err()); + + wsc_json = json_object_new_object(); + if (!wsc_json) { + agnt_warn(LOG_APCFG, "%s: failed to create json obj, error:%s. ", + __func__, json_util_get_last_err()); + return -1; + } + } + + /* Get the radios array */ + ret = json_object_object_get_ex(wsc_json, "radios", &radios); + if (!ret) { + /* Create radios array if not found */ + radios = json_object_new_array(); + if (!radios) { + agnt_warn(LOG_APCFG, "%s: failed to add radio array, error:%s. ", + __func__, json_util_get_last_err()); + goto out_radio; + } + json_object_object_add(wsc_json, "radios", radios); + } else if (!json_object_is_type(radios, json_type_array)) { + agnt_warn(LOG_APCFG, "%s: file: %s has wrong format\n", + __func__, AUTOCFG_FILE); + goto out_radio; + } + + /* Check if radio already exists in radio array */ + len = json_object_array_length(radios); + for (i = 0; i < len; i++) { + struct json_object *t; + uint8_t radio_mac[6]; + const char *p; + + t = json_object_array_get_idx(radios, i); + if (!t) { + agnt_dbg(LOG_APCFG, "%s: couldn't get entry:%d from radio array", __func__, i); + continue; + } + + p = json_get_string(t, "macaddr"); + if (!p) { + agnt_dbg(LOG_APCFG, "%s: couldn't get macaddr from entry:%d", __func__, i); + continue; + } + + if (!hwaddr_aton(p, radio_mac)) { + agnt_dbg(LOG_APCFG, "%s: couldn't convert macaddr from entry:%d", __func__, i); + continue; + } + + if (!memcmp(macaddr, radio_mac, 6)) { + /* radio entry already present in file */ + radio_obj = t; + break; + } + } + + /* Create new radio object if not found */ + if (!radio_obj) { + char radiostr[18] = {0}; + struct json_object *val; + + radio_obj = json_object_new_object(); + if (WARN_ON(!radio_obj)) + goto out_radio; + + /* radio mac */ + hwaddr_ntoa(macaddr, radiostr); + val = json_object_new_string(radiostr); + if (!val) { + json_object_put(radio_obj); + goto out_radio; + } + json_object_object_add(radio_obj, "macaddr", val); + + /* Add radio object to radios array */ + json_object_array_add(radios, radio_obj); + } + + /* Get the sha256 string */ + ret = json_object_object_get_ex(radio_obj, "m2_sha256", &sha256_obj); + if (ret) { + /* Remove old SHA first */ + json_object_object_del(radio_obj, "m2_sha256"); + } + + /* Create sha256 string */ + sha256_str = calloc(SHA256_LENGTH * 2 + 1, sizeof(char)); + if (!sha256_str) + goto out_radio; + + btostr(sha256, SHA256_LENGTH, sha256_str); + + sha256_obj = json_object_new_string(sha256_str); + if (!sha256_obj) { + json_object_put(radio_obj); + goto free; + } + json_object_object_add(radio_obj, "m2_sha256", sha256_obj); + + /* Update sha256 file */ + json_object_to_file(AUTOCFG_FILE, wsc_json); + + agnt_dbg(LOG_APCFG, "%s: Wrote autoconfig SHA256: %02x%02x%02x%02x%02x%02x... to file %s\n", + __func__, + sha256[0], sha256[1], + sha256[2], sha256[3], + sha256[4], sha256[5], + AUTOCFG_FILE); + +free: + free(sha256_str); +out_radio: + json_object_put(wsc_json); + + return 0; +} + +int autoconfig_radio_hash_from_file(uint8_t *macaddr, uint8_t *sha256_out) +{ + struct blob_buf radios = { 0 }; + struct blob_attr *b; + static const struct blobmsg_policy attr[] = { + [0] = { .name = "radios", .type = BLOBMSG_TYPE_ARRAY }, + }; + struct blob_attr *tb[ARRAY_SIZE(attr)]; + int rem; + int ret = 0; + + blob_buf_init(&radios, 0); + + if (!blobmsg_add_json_from_file(&radios, AUTOCFG_FILE)) { + agnt_dbg(LOG_APCFG, "Failed to parse %s\n", AUTOCFG_FILE); + ret = -1; + goto out; + } + + blobmsg_parse(attr, ARRAY_SIZE(attr), tb, blob_data(radios.head), blob_len(radios.head)); + + if (!tb[0]) { + ret = -1; + goto out; + } + + blobmsg_for_each_attr(b, tb[0], rem) { + static const struct blobmsg_policy radio_attr[2] = { + [0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING }, + [1] = { .name = "m2_sha256", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb1[ARRAY_SIZE(radio_attr)]; + char radio_str[18] = {0}; + uint8_t radio_mac[6] = {0}; + char *sh256_str; + int blen; + + blobmsg_parse(radio_attr, ARRAY_SIZE(radio_attr), tb1, blobmsg_data(b), blob_len(b)); + if (!tb1[0] || !tb1[1]) + continue; + + strncpy(radio_str, blobmsg_data(tb1[0]), sizeof(radio_str) - 1); + if (!hwaddr_aton(radio_str, radio_mac)) { + agnt_dbg(LOG_APCFG, "Failed to convert macaddr %s\n", radio_str); + continue; + } + + if (memcmp(macaddr, radio_mac, 6)) + continue; + + sh256_str = blobmsg_get_string(tb1[1]); + if (!sh256_str) { + agnt_err(LOG_APCFG, "%s: No valid sha256\n", __func__); + ret = -1; + break; + } + + blen = strlen(sh256_str) / 2; + if (blen > SHA256_LENGTH) { + agnt_err(LOG_APCFG, "%s: SHA256 string too long\n", __func__); + ret = -1; + break; + } + + strtob(sh256_str, blen, sha256_out); + } + + dbg("%s: Read autoconfig SHA256 for radio " MACFMT ": %02x%02x%02x%02x%02x%02x...\n", + __func__, MAC2STR(macaddr), + sha256_out[0], sha256_out[1], + sha256_out[2], sha256_out[3], + sha256_out[4], sha256_out[5]); + +out: + blob_buf_free(&radios); + + return ret; +} diff --git a/src/autoconfig.h b/src/autoconfig.h new file mode 100644 index 0000000000000000000000000000000000000000..93f45dd367d380220f1c0adf7124909f26a5bea0 --- /dev/null +++ b/src/autoconfig.h @@ -0,0 +1,52 @@ +#ifndef AUTOCONFIG_H +#define AUTOCONFIG_H + +#include <stdbool.h> +#include <stdint.h> + +#include "utils/libcrypto.h" + +#define HEARTBEAT_AUTOCFG_INTERVAL 70 + +#define AUTOCFG_FILE "/etc/multiap/wsc_m2.json" + +struct wifi_radio_element; + +enum autocfg_state { + AUTOCFG_HEARTBEAT, + AUTOCFG_ACTIVE +}; + +enum autocfg_diff { + DIFF_NONE = 0, + DIFF_WSC_M2 = 1 << 0, +#if (EASYMESH_VERSION >= 6) + DIFF_AP_MLD = 1 << 1, + DIFF_BSTA_MLD = 1 << 2, +#endif + DIFF_ERR = 255, +}; + +struct wsc_data { + uint8_t *m1_frame; + uint16_t m1_size; + struct wsc_key *key; + uint8_t sha256[SHA256_LENGTH]; +}; + +void autoconfig_clean_wsc_hash(struct wsc_data *wsc); +void autoconfig_update_wsc_hash(struct wsc_data *data, uint8_t *new_sha256); +int autoconfig_wsc_m2_diff(struct wifi_radio_element *re, + struct tlv *tlvs[][TLV_MAXNUM], + size_t tlvs_size, + uint8_t *wsc_m2_sha256); + +int autoconfig_radio_hash_to_file(uint8_t *macaddr, uint8_t *sha256); +int autoconfig_radio_hash_from_file(uint8_t *macaddr, uint8_t *sha256_out); +#if (EASYMESH_VERSION >= 6) +int autoconfig_ap_mld_hash_to_file(uint8_t *sha256); +int autoconfig_ap_mld_hash_from_file(uint8_t *sha256_out); +int autoconfig_bsta_mld_hash_to_file(uint8_t *sha256); +int autoconfig_bsta_mld_hash_from_file(uint8_t *sha256_out); +#endif +#endif diff --git a/src/utils/libcrypto.c b/src/utils/libcrypto.c new file mode 100644 index 0000000000000000000000000000000000000000..3d5c1605e364aa0af975a41aa507805ec35853d3 --- /dev/null +++ b/src/utils/libcrypto.c @@ -0,0 +1,42 @@ +/* + * liblcrytpo.c - implements crytpographic function wrappers + * + * Copyright (C) 2025 iopsys Software Solutions AB. All rights reserved. + * + */ + +#include <string.h> +#include <openssl/evp.h> + +#include "libcrypto.h" + +bool update_sha256_hash(void *data, int length, uint8_t *sha256_out) +{ + bool ret = false; + uint8_t new_sha256[SHA256_LENGTH] = {0}; + EVP_MD_CTX *ctx; + + ctx = EVP_MD_CTX_new(); + if (!ctx) + return true; + + if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) + goto error_cleanup; + + if (!EVP_DigestUpdate(ctx, data, length)) + goto error_cleanup; + + if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL)) + goto error_cleanup; + + if (memcmp(sha256_out, new_sha256, SHA256_LENGTH)) { + memcpy(sha256_out, new_sha256, SHA256_LENGTH); + ret = true; + } + +error_cleanup: + EVP_MD_CTX_free(ctx); + return ret; +} + + diff --git a/src/utils/libcrypto.h b/src/utils/libcrypto.h new file mode 100644 index 0000000000000000000000000000000000000000..cfabe32d492f27c25163aedf32e7cb8b59dff87f --- /dev/null +++ b/src/utils/libcrypto.h @@ -0,0 +1,16 @@ +/* + * libcrypto.h - crypto utility functions header file + * + * Copyright (C) 2025 iopsys Software Solutions AB. All rights reserved. + * + */ + +#ifndef LIBCRYPTO_H +#define LIBCRYPTO_H + +#include <stdbool.h> + +#define SHA256_LENGTH 32 + +bool update_sha256_hash(void *data, int length, uint8_t *sha256_out); +#endif /* LIBCRYPTO_H */