diff --git a/components/esp_wifi/include/esp_wifi_types_generic.h b/components/esp_wifi/include/esp_wifi_types_generic.h index fc80352a68c..f3abe1aefcf 100644 --- a/components/esp_wifi/include/esp_wifi_types_generic.h +++ b/components/esp_wifi/include/esp_wifi_types_generic.h @@ -604,9 +604,8 @@ typedef struct { uint8_t scan_time; /**< Scan time in seconds while searching for a NAN cluster */ uint16_t warm_up_sec; /**< Warm up time before assuming NAN Anchor Master role */ bool disable_random_mac;/**< Disable the MAC Randomisation in NAN */ - uint8_t nik[ESP_WIFI_NAN_NIK_LEN]; /**< Optional NIK. Auto-generated when nik_valid is false. */ - uint8_t nik_valid: 1; /**< NIK present in nik[] and should be used as-is. */ - uint8_t reserved: 7; /**< Reserved for future use. */ + bool reset_current_nvs_creds; /**< Erase all NAN credentials (own NIK and cached peer NIK/NPK entries) saved in NVS before starting. */ + bool use_nvs_for_caching; /**< Persist newly-learned peer credentials (NIK/NPK) to NVS so they survive across reboots. */ } wifi_nan_sync_config_t; /** @@ -874,7 +873,9 @@ typedef struct { #define ESP_WIFI_NDP_ROLE_RESPONDER 2 /**< Responder role for NAN Data Path */ #define ESP_WIFI_NAN_NDP_PMK_LEN 32 /**< Length of NAN Datapath PMK */ +#define ESP_WIFI_NAN_NPK_LEN ESP_WIFI_NAN_NDP_PMK_LEN /**< Length of NAN Pairwise Key (same as NDP PMK) */ #define ESP_WIFI_NAN_NDP_PMKID_LEN 16 /**< Length of NAN Datapath PMKID */ +#define ESP_WIFI_NAN_MAX_PEER_CREDS 2 /**< nik_valid) { - memcpy(s_nan_ctx.own_nik, nan_cfg->nik, ESP_WIFI_NAN_NIK_LEN); - s_nan_ctx.own_nik_valid = true; - } else { + s_nan_ctx.own_nik_valid = false; + s_nan_ctx.num_peer_creds = 0; + memset(s_nan_ctx.peer_creds, 0, sizeof(s_nan_ctx.peer_creds)); + s_nan_ctx.use_nvs_for_caching = nan_cfg->use_nvs_for_caching; + + if (nan_cfg->reset_current_nvs_creds) { + /* Start from a clean slate: drop every credential persisted in NVS. */ + esp_wifi_nan_erase_all_creds(); + } else if (esp_wifi_nan_load_saved_creds(s_nan_ctx.own_nik, &s_nan_ctx.own_nik_valid, + s_nan_ctx.peer_creds, &s_nan_ctx.num_peer_creds) != ESP_OK) { + ESP_LOGW(TAG, "Failed to load saved NAN credentials"); + s_nan_ctx.own_nik_valid = false; + s_nan_ctx.num_peer_creds = 0; + } + + if (!s_nan_ctx.own_nik_valid) { if (os_get_random(s_nan_ctx.own_nik, ESP_WIFI_NAN_NIK_LEN) != 0) { NAN_DATA_UNLOCK(); ESP_LOGE(TAG, "Failed to generate NAN NIK"); return ESP_FAIL; } s_nan_ctx.own_nik_valid = true; + /* Persist the freshly generated NIK only when NVS caching is enabled; + * otherwise the identity stays ephemeral for this session. */ + if (s_nan_ctx.use_nvs_for_caching) { + esp_wifi_nan_save_own_nik(s_nan_ctx.own_nik); + } } /* Drop the cached NIRA tag; it was derived from the previous NIK. */ s_nan_ctx.nira_cached = false; diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h b/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h index 40976b5e45c..25a0df128fd 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h @@ -17,6 +17,8 @@ #include "esp_wifi_types_generic.h" #include "esp_private/wifi.h" #include "esp_nan.h" +#include "utils/common.h" /* u8/u16 typedefs required by esp_wifi_driver.h */ +#include "esp_wifi_driver.h" /* wifi_nan_peer_creds_t + NAN credential NVS APIs */ #include "os.h" #ifdef __cplusplus @@ -296,6 +298,12 @@ typedef struct { uint8_t cached_nira_nonce[8]; uint8_t cached_nira_tag[8]; bool nira_cached; + /* Peer NIK/NPK credentials loaded from NVS at start and refreshed as peers + * are paired. Drives NIRA identity resolution; optionally persisted to NVS + * when @c use_nvs_for_caching is set. */ + wifi_nan_peer_creds_t peer_creds[ESP_WIFI_NAN_MAX_PEER_CREDS]; + uint8_t num_peer_creds; + bool use_nvs_for_caching; #endif #ifdef CONFIG_ESP_WIFI_PASN_SUPPORT struct nan_pasn_data *nan_pasn_data; diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c index 1fe18bb918d..758c876c283 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c @@ -847,6 +847,39 @@ static void nan_pairing_key_installed_cb(const uint8_t *peer_nmi, } } +/* Insert or refresh a (peer NIK, NPK) entry in the in-RAM credential cache used + * for NIRA identity resolution. Caller holds NAN_DATA_LOCK. A @a npk of NULL + * stores a zeroed key. When the cache is full the oldest entry (slot 0) is + * reused. */ +static void nan_app_update_peer_creds(const uint8_t *peer_nik, const uint8_t *npk) +{ + wifi_nan_peer_creds_t *slot = NULL; + + /* Reuse the slot already holding this NIK, if any. */ + for (uint8_t i = 0; i < s_nan_ctx.num_peer_creds; i++) { + if (s_nan_ctx.peer_creds[i].is_valid && + os_memcmp(s_nan_ctx.peer_creds[i].peer_nik, peer_nik, ESP_WIFI_NAN_NIK_LEN) == 0) { + slot = &s_nan_ctx.peer_creds[i]; + break; + } + } + if (!slot) { + if (s_nan_ctx.num_peer_creds < ESP_WIFI_NAN_MAX_PEER_CREDS) { + slot = &s_nan_ctx.peer_creds[s_nan_ctx.num_peer_creds++]; + } else { + slot = &s_nan_ctx.peer_creds[0]; + } + } + + memcpy(slot->peer_nik, peer_nik, ESP_WIFI_NAN_NIK_LEN); + if (npk) { + memcpy(slot->npk, npk, ESP_WIFI_NAN_NPK_LEN); + } else { + memset(slot->npk, 0, ESP_WIFI_NAN_NPK_LEN); + } + slot->is_valid = true; +} + void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, const uint8_t *peer_mac, const uint8_t *shared_key_attr, @@ -891,6 +924,8 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, bool already_had_nik = false; bool pairing_completed = false; uint8_t own_svc_id_to_disable = 0; + bool persist_creds = false; + uint8_t persist_npk[ESP_WIFI_NAN_NPK_LEN] = {0}; NAN_DATA_LOCK(); struct peer_svc_info *p_peer_svc = nan_find_peer_svc_exact(svc_id, peer_svc_id, peer_mac); @@ -903,6 +938,15 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, ESP_LOGI(TAG, "Stored peer NIK from " MACSTR " (cipher_ver=%u, lifetime=%u s)", MAC2STR(peer_mac), cipher_ver, lifetime_sec); + /* Refresh the NIRA credential cache with this peer's NIK and, if the + * pairing record is available, its NPK. */ + const struct nan_paired_peer *paired = nan_app_find_paired_peer(peer_mac); + if (paired) { + memcpy(persist_npk, paired->nd_pmk, ESP_WIFI_NAN_NPK_LEN); + } + nan_app_update_peer_creds(nik, paired ? paired->nd_pmk : NULL); + persist_creds = s_nan_ctx.use_nvs_for_caching; + if (!already_had_nik) { pairing_completed = true; own_svc_id_to_disable = p_peer_svc->own_svc_id; @@ -910,6 +954,11 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, } NAN_DATA_UNLOCK(); + /* Persist outside the lock; NVS writes can block. */ + if (persist_creds) { + esp_wifi_nan_save_creds_for_peer(nik, persist_npk); + } + /* Invoke blocking calls outside NAN_DATA_LOCK to avoid deadlock. */ if (pairing_completed) { wifi_event_nan_pairing_complete_t evt = {0}; @@ -959,8 +1008,7 @@ bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_at uint8_t expected_tag[NAN_NIRA_TAG_LEN]; const uint8_t *nonce; const uint8_t *received_tag; - struct peer_svc_info *p_peer_svc; - bool match; + bool match = false; if (!peer_mac || !nira_attr) { return false; @@ -975,26 +1023,28 @@ bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_at nonce = nira_attr + 4; received_tag = nira_attr + 4 + NAN_NIRA_NONCE_LEN; + /* The peer derives its NIRA tag from one of its NIKs; the sending MAC may be + * randomised, so resolve the identity by trying every cached peer NIK. */ NAN_DATA_LOCK(); - p_peer_svc = nan_find_peer_svc(0, 0, peer_mac); - if (!p_peer_svc || !p_peer_svc->has_nik) { - NAN_DATA_UNLOCK(); - ESP_LOGD(TAG, "NIRA verify: no stored NIK for "MACSTR, MAC2STR(peer_mac)); - return false; - } - - if (nan_pairing_derive_nira_tag(p_peer_svc->peer_nik, peer_mac, nonce, expected_tag) != 0) { - NAN_DATA_UNLOCK(); - ESP_LOGE(TAG, "NIRA verify: tag derivation failed for "MACSTR, MAC2STR(peer_mac)); - return false; + for (uint8_t i = 0; i < s_nan_ctx.num_peer_creds; i++) { + if (!s_nan_ctx.peer_creds[i].is_valid) { + continue; + } + if (nan_pairing_derive_nira_tag(s_nan_ctx.peer_creds[i].peer_nik, peer_mac, + nonce, expected_tag) != 0) { + continue; + } + if (os_memcmp_const(expected_tag, received_tag, NAN_NIRA_TAG_LEN) == 0) { + match = true; + break; + } } NAN_DATA_UNLOCK(); - match = (os_memcmp_const(expected_tag, received_tag, NAN_NIRA_TAG_LEN) == 0); if (match) { ESP_LOGD(TAG, "NIRA verify: OK for "MACSTR, MAC2STR(peer_mac)); } else { - ESP_LOGW(TAG, "NIRA verify: tag mismatch for "MACSTR, MAC2STR(peer_mac)); + ESP_LOGW(TAG, "NIRA verify: no matching NIK for "MACSTR, MAC2STR(peer_mac)); } return match; } diff --git a/components/wpa_supplicant/CMakeLists.txt b/components/wpa_supplicant/CMakeLists.txt index 8a9b5829334..85eef8e5246 100644 --- a/components/wpa_supplicant/CMakeLists.txt +++ b/components/wpa_supplicant/CMakeLists.txt @@ -314,7 +314,11 @@ endif() if(CONFIG_ESP_WIFI_ENABLE_SAE_H2E) target_compile_definitions(${COMPONENT_LIB} PRIVATE CONFIG_SAE_H2E) endif() -if(CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT) +# PASN reuses the SAE module (PWE/crypto + comeback-token mechanism), so +# CONFIG_SAE must be defined whenever SoftAP-SAE or PASN is enabled. AP-side SAE +# authentication paths are additionally gated on CONFIG_ESP_WIFI_SOFTAP_SUPPORT +# in the sources so they stay out of PASN-only (SoftAP-disabled) builds. +if(CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT OR CONFIG_ESP_WIFI_PASN_SUPPORT) target_compile_definitions(${COMPONENT_LIB} PRIVATE CONFIG_SAE) endif() if(CONFIG_ESP_WIFI_WPA3_COMPATIBLE_SUPPORT) diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h b/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h index 59fd3c62d98..8d6c7382070 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h @@ -240,6 +240,12 @@ typedef enum { NAN_KEY_NM_TK, } nan_key_type_t; +typedef struct { + uint8_t peer_nik[ESP_WIFI_NAN_NIK_LEN]; /**< Peer's NAN Identity Key (16 bytes) */ + uint8_t npk[ESP_WIFI_NAN_NPK_LEN]; /**< NAN Pairwise Key / NCS-SK PMK (32 bytes) */ + bool is_valid; +} wifi_nan_peer_creds_t; + typedef wifi_scan_channel_bitmap_t channel_bitmap_t; uint8_t *esp_wifi_ap_get_prof_pmk_internal(void); @@ -335,5 +341,11 @@ uint8_t esp_wifi_op_class_supported_internal(uint8_t op_class, uint8_t min_chan, bool esp_wifi_is_wpa3_compatible_mode_enabled(uint8_t if_index); uint8_t esp_wifi_ap_get_owe_config_internal(void); esp_err_t esp_nan_disable_pairing(uint8_t svc_id); +esp_err_t esp_wifi_nan_load_saved_creds(uint8_t own_nik[ESP_WIFI_NAN_NIK_LEN], bool *own_nik_valid, + wifi_nan_peer_creds_t peer_creds[ESP_WIFI_NAN_MAX_PEER_CREDS], uint8_t *num_peer_creds); +esp_err_t esp_wifi_nan_save_own_nik(const uint8_t own_nik[ESP_WIFI_NAN_NIK_LEN]) ; +esp_err_t esp_wifi_nan_save_creds_for_peer(const uint8_t peer_nik[ESP_WIFI_NAN_NIK_LEN], + const uint8_t npk[ESP_WIFI_NAN_NPK_LEN]); +esp_err_t esp_wifi_nan_erase_all_creds(void); #endif /* _ESP_WIFI_DRIVER_H_ */ diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wpa3.c b/components/wpa_supplicant/esp_supplicant/src/esp_wpa3.c index 895955d6d28..32430eb7445 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wpa3.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wpa3.c @@ -441,7 +441,11 @@ void esp_wifi_unregister_wpa3_cb(void) } #endif /* CONFIG_WPA3_SAE */ -#ifdef CONFIG_SAE +/* AP-side (hostap) SAE authentication. CONFIG_SAE may also be enabled for PASN + * with SoftAP disabled, but this block both defines and calls SoftAP-only + * primitives (esp_send_sae_auth_reply, handle_auth_sae), so additionally gate + * it on CONFIG_ESP_WIFI_SOFTAP_SUPPORT. */ +#if defined(CONFIG_SAE) && defined(CONFIG_ESP_WIFI_SOFTAP_SUPPORT) static TaskHandle_t g_wpa3_hostap_task_hdl = NULL; static QueueHandle_t g_wpa3_hostap_evt_queue = NULL; @@ -861,4 +865,4 @@ void esp_wifi_register_wpa3_ap_cb(struct wpa_funcs *wpa_cb) wpa_cb->wpa3_hostap_handle_auth = wpa3_hostap_handle_auth; } -#endif /* CONFIG_SAE */ +#endif /* CONFIG_SAE && CONFIG_ESP_WIFI_SOFTAP_SUPPORT */ diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wpa3_i.h b/components/wpa_supplicant/esp_supplicant/src/esp_wpa3_i.h index 9f5e49018d2..26486207e3b 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wpa3_i.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wpa3_i.h @@ -6,6 +6,7 @@ #ifndef ESP_WPA3_H #define ESP_WPA3_H +#include "sdkconfig.h" #include "esp_wifi_driver.h" #ifdef CONFIG_WPA3_SAE @@ -27,7 +28,10 @@ static inline void esp_wpa3_free_sae_data(void) #endif /* CONFIG_WPA3_SAE */ -#ifdef CONFIG_SAE +/* AP-side (hostap) SAE definitions require SoftAP; CONFIG_SAE alone may be set + * for PASN. The #else branch provides a no-op esp_wifi_register_wpa3_ap_cb() + * stub so callers link in PASN-only (SoftAP-disabled) builds. */ +#if defined(CONFIG_SAE) && defined(CONFIG_ESP_WIFI_SOFTAP_SUPPORT) enum SIG_WPA3_TASK { SIG_WPA3_RX_COMMIT, SIG_WPA3_RX_CONFIRM, @@ -58,12 +62,12 @@ void esp_wifi_register_wpa3_ap_cb(struct wpa_funcs *wpa_cb); int wpa3_hostap_auth_init(void *data); bool wpa3_hostap_auth_deinit(void); -#else /* CONFIG_SAE */ +#else /* CONFIG_SAE && CONFIG_ESP_WIFI_SOFTAP_SUPPORT */ static inline void esp_wifi_register_wpa3_ap_cb(struct wpa_funcs *wpa_cb) { wpa_cb->wpa3_hostap_handle_auth = NULL; } -#endif /* CONFIG_SAE */ +#endif /* CONFIG_SAE && CONFIG_ESP_WIFI_SOFTAP_SUPPORT */ #endif /* ESP_WPA3_H */ diff --git a/components/wpa_supplicant/src/ap/ieee802_11.c b/components/wpa_supplicant/src/ap/ieee802_11.c index 99327c8acbb..4ba44dad40e 100644 --- a/components/wpa_supplicant/src/ap/ieee802_11.c +++ b/components/wpa_supplicant/src/ap/ieee802_11.c @@ -28,7 +28,10 @@ #define OWE_DH_GRP19 19 #endif -#ifdef CONFIG_SAE +/* AP-side SAE authentication. Requires SoftAP: CONFIG_SAE may also be enabled + * for PASN (SoftAP disabled), but this block calls SoftAP-only primitives + * (e.g. esp_send_sae_auth_reply), so gate it on CONFIG_ESP_WIFI_SOFTAP_SUPPORT. */ +#if defined(CONFIG_SAE) && defined(CONFIG_ESP_WIFI_SOFTAP_SUPPORT) static void sae_set_state(struct sta_info *sta, enum sae_state state, const char *reason) @@ -778,7 +781,7 @@ queued: } -#endif /* CONFIG_SAE */ +#endif /* CONFIG_SAE && CONFIG_ESP_WIFI_SOFTAP_SUPPORT */ u16 wpa_res_to_status_code(enum wpa_validate_result res) {