diff --git a/components/esp_wifi/include/esp_private/wifi.h b/components/esp_wifi/include/esp_private/wifi.h index cdc7a9ad66d..9331077f919 100644 --- a/components/esp_wifi/include/esp_private/wifi.h +++ b/components/esp_wifi/include/esp_private/wifi.h @@ -106,6 +106,7 @@ struct nan_cb_peer_info { uint16_t ssi_len; /**< SSI length in bytes */ wifi_nan_peer_sdf_security_t *peer_security_params; /**< Peer's discovery security params parsed from SDF */ nan_vendor_ie_t *vendor_ie; /**< Vendor-specific IE, if any */ + bool nira_verified; /**< true when received NIRA tag verified against cached NIK */ }; /* NDP Peer info parsed from NAF. */ @@ -196,6 +197,7 @@ struct nan_sync_callbacks { uint32_t (* get_nira_len)(void); int (* construct_nira)(uint8_t *frm); bool (*verify_nira)(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_attr_len); + bool (*peer_nik_cached)(uint8_t *peer_mac); }; /* Host helpers for NAN encrypted-datapath, registered via @@ -1201,6 +1203,19 @@ uint32_t esp_nan_get_nira_len(void); */ int esp_nan_construct_nira(uint8_t *frm); +/** + * @brief Construct a NAN Cipher Suite Info Attribute (CSIA) + * + * @param[out] frm Buffer to write the attribute to + * @param[in] pub_id Publish service instance id + * @param[in] own_csid_bitmap Locally supported cipher suite bitmap + * @param[in] peer_csid_bitmap Peer cipher suite bitmap, or 0 to use own bitmap + * + * @return Number of bytes written, or 0 on failure/no cipher suite + */ +int esp_nan_construct_csia(uint8_t *frm, uint8_t pub_id, + uint16_t own_csid_bitmap, uint16_t peer_csid_bitmap); + /** * @brief Verify a received NAN Identity Resolution Attribute (NIRA) * @@ -1212,6 +1227,24 @@ int esp_nan_construct_nira(uint8_t *frm); */ bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_attr_len); +/** + * @brief Verify a received NIRA and resolve the matched own service id + * + * Behaves like @ref esp_nan_verify_nira but additionally outputs the local + * service instance id the verifying NIK maps to, used to anchor a pairing + * verify-session flag. @p own_inst_id is set to 0 when the identity does not + * resolve to an active local service. + * + * @param[in] peer_mac NMI of the sender + * @param[in] nira_attr NIRA attribute buffer + * @param[in] nira_attr_len Attribute length in bytes + * @param[out] own_inst_id Resolved own service instance id (0 if none) + * + * @return true if the tag matches, false otherwise + */ +bool esp_nan_verify_nira_get_own_svc(uint8_t *peer_mac, uint8_t *nira_attr, + uint16_t nira_attr_len, uint8_t *own_inst_id); + /** * @brief Get the time information from the MAC clock. The time is precise only if modem sleep or light sleep is not enabled. * diff --git a/components/esp_wifi/include/esp_wifi_types_generic.h b/components/esp_wifi/include/esp_wifi_types_generic.h index f44b8590de5..1a92443aa5a 100644 --- a/components/esp_wifi/include/esp_wifi_types_generic.h +++ b/components/esp_wifi/include/esp_wifi_types_generic.h @@ -1305,8 +1305,6 @@ typedef enum { WIFI_EVENT_DPP_URI_READY, /**< DPP URI is ready through Bootstrapping */ WIFI_EVENT_DPP_CFG_RECVD, /**< DPP Configuration Response; payload is wifi_event_dpp_config_received_t */ WIFI_EVENT_DPP_FAILED, /**< DPP failed */ - WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ - WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, /**< NAN Pairing Bootstrapping completed (success/failure) */ WIFI_EVENT_NAN_PAIRING_INDICATION, /**< Received NAN Pairing indication (reserved) */ WIFI_EVENT_NAN_PAIRING_CONFIRM, /**< NAN pairing completed after NIK follow-up exchange */ WIFI_EVENT_NAN_CLUSTER_JOIN, /**< Posted when the device joins, starts, or merges into a NAN cluster */ @@ -1637,38 +1635,6 @@ typedef struct { uint8_t init_ndi[6]; /**< Initiator's NAN Data Interface MAC */ } wifi_event_ndp_terminated_t; -/** - * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_INDICATION event - * - * Posted when a NAN Pairing Bootstrapping Request is received from a peer. - * The application should respond using esp_wifi_nan_bootstrap_response(). - */ -typedef struct { - uint8_t peer_svc_id; /**< Peer's service instance id */ - uint8_t own_svc_id; /**< Own service instance id */ - uint8_t peer_nmi[6]; /**< Peer's NAN Management Interface MAC */ - uint16_t selected_method; /**< Bootstrapping method selected by initiator (one WIFI_NAN_BOOTSTRAP_* bit) */ - uint8_t is_comeback; /**< 1 if this is a comeback retry with cookie */ - uint32_t cookie; /**< Comeback cookie from initiator (0 if none) */ -} wifi_event_nan_bootstrap_indication_t; - -/** - * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED event - * - * Posted when a NAN Pairing Bootstrapping Response is received, - * or when the bootstrapping handshake completes/fails. - */ -typedef struct { - uint8_t status; /**< 0=Accepted, 1=Rejected, 2=Comeback (wifi_nan_pairing_status_t) */ - uint8_t peer_svc_id; /**< Peer's service instance id */ - uint8_t own_svc_id; /**< Own service instance id */ - uint8_t peer_nmi[6]; /**< Peer's NAN Management Interface MAC */ - uint16_t matched_method; /**< Matched bootstrapping method, one WIFI_NAN_BOOTSTRAP_* bit (valid if accepted) */ - uint8_t reason_code; /**< Rejection reason (valid if rejected) */ - uint16_t comeback_after; /**< Comeback deferral time in TUs (valid if comeback) */ - uint32_t cookie; /**< Comeback cookie from responder (0 if none) */ -} wifi_event_nan_bootstrap_complete_t; - /** * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_CONFIRM event */ diff --git a/components/esp_wifi/lib b/components/esp_wifi/lib index 9ff50d7c694..36bb033d2f9 160000 --- a/components/esp_wifi/lib +++ b/components/esp_wifi/lib @@ -1 +1 @@ -Subproject commit 9ff50d7c6943a64f11a5962053da1be6f5687493 +Subproject commit 36bb033d2f9d89703e548e4e657a76e0b01bb2b2 diff --git a/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h b/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h index b748bb9291d..3980683f70a 100644 --- a/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h +++ b/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h @@ -1305,8 +1305,6 @@ typedef enum { WIFI_EVENT_DPP_URI_READY, /**< DPP URI is ready through Bootstrapping */ WIFI_EVENT_DPP_CFG_RECVD, /**< DPP Configuration Response; payload is wifi_event_dpp_config_received_t */ WIFI_EVENT_DPP_FAILED, /**< DPP failed */ - WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ - WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, /**< NAN Pairing Bootstrapping completed (success/failure) */ WIFI_EVENT_NAN_PAIRING_INDICATION, /**< Received NAN Pairing indication (reserved) */ WIFI_EVENT_NAN_PAIRING_CONFIRM, /**< NAN pairing completed after NIK follow-up exchange */ WIFI_EVENT_NAN_CLUSTER_JOIN, /**< Posted when the device joins, starts, or merges into a NAN cluster */ @@ -1637,38 +1635,6 @@ typedef struct { uint8_t init_ndi[6]; /**< Initiator's NAN Data Interface MAC */ } wifi_event_ndp_terminated_t; -/** - * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_INDICATION event - * - * Posted when a NAN Pairing Bootstrapping Request is received from a peer. - * The application should respond using esp_wifi_nan_bootstrap_response(). - */ -typedef struct { - uint8_t peer_svc_id; /**< Peer's service instance id */ - uint8_t own_svc_id; /**< Own service instance id */ - uint8_t peer_nmi[6]; /**< Peer's NAN Management Interface MAC */ - uint16_t selected_method; /**< Bootstrapping method selected by initiator (one WIFI_NAN_BOOTSTRAP_* bit) */ - uint8_t is_comeback; /**< 1 if this is a comeback retry with cookie */ - uint32_t cookie; /**< Comeback cookie from initiator (0 if none) */ -} wifi_event_nan_bootstrap_indication_t; - -/** - * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED event - * - * Posted when a NAN Pairing Bootstrapping Response is received, - * or when the bootstrapping handshake completes/fails. - */ -typedef struct { - uint8_t status; /**< 0=Accepted, 1=Rejected, 2=Comeback (wifi_nan_pairing_status_t) */ - uint8_t peer_svc_id; /**< Peer's service instance id */ - uint8_t own_svc_id; /**< Own service instance id */ - uint8_t peer_nmi[6]; /**< Peer's NAN Management Interface MAC */ - uint16_t matched_method; /**< Matched bootstrapping method, one WIFI_NAN_BOOTSTRAP_* bit (valid if accepted) */ - uint8_t reason_code; /**< Rejection reason (valid if rejected) */ - uint16_t comeback_after; /**< Comeback deferral time in TUs (valid if comeback) */ - uint32_t cookie; /**< Comeback cookie from responder (0 if none) */ -} wifi_event_nan_bootstrap_complete_t; - /** * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_CONFIRM event */ diff --git a/components/esp_wifi/wifi_apps/include/apps_private/wifi_apps_private.h b/components/esp_wifi/wifi_apps/include/apps_private/wifi_apps_private.h index 243ef96af99..7c67345757d 100644 --- a/components/esp_wifi/wifi_apps/include/apps_private/wifi_apps_private.h +++ b/components/esp_wifi/wifi_apps/include/apps_private/wifi_apps_private.h @@ -53,6 +53,8 @@ void esp_nan_action_stop(void); #ifdef CONFIG_ESP_WIFI_NAN_PAIRING +#include "esp_private/wifi_types.h" + #define WIFI_NAN_PAIRING_REASON_NIK_FUP_TIMEOUT 1 /**< Local reason: peer NIK follow-up not received within timeout. See Wi-Fi Aware v4.0 §7.6.4.2 for the NIK-exchange procedure. */ @@ -76,9 +78,46 @@ typedef struct { uint8_t peer_svc_id; uint8_t peer_nmi[6]; enum nan_pairing_role self_role; + uint8_t pairing_verification: 1; /**< 1 - PASN verify (re-pair), 0 - PASN auth (bootstrap pairing) */ + uint8_t reserved: 7; union pairing_cred_t cred; } wifi_nan_pairing_config_t; +/** + * @brief NAN Pairing Bootstrap frame event (request or response). + * + * @p type is WIFI_NAN_NPBA_TYPE_REQUEST (1) for a bootstrapping request from a peer, + * or WIFI_NAN_NPBA_TYPE_RESPONSE (2) for a bootstrapping response. + * For requests, @p methods is the selected bootstrapping method. + * For responses, @p methods is the matched method, @p status and @p reason_code are valid. + */ +typedef struct { + uint8_t type; /**< WIFI_NAN_NPBA_TYPE_REQUEST or WIFI_NAN_NPBA_TYPE_RESPONSE */ + uint8_t peer_svc_id; /**< Peer's service instance id */ + uint8_t own_svc_id; /**< Own service instance id */ + uint8_t peer_nmi[6]; /**< Peer's NAN Management Interface MAC */ + uint16_t methods; /**< selected_method (request) or matched_method (response) */ + uint8_t status; /**< wifi_nan_pairing_status_t; valid for response */ + uint8_t reason_code; /**< Rejection reason; valid for response when rejected */ +} wifi_nan_bootstrap_event_t; + +/** + * @brief Callback invoked when a NAN Pairing Bootstrap request or response is received. + * + * @param evt Bootstrap frame event data. + */ +typedef void (*esp_nan_app_bootstrap_cb_t)(const wifi_nan_bootstrap_event_t *evt); + +/** + * @brief Set the callback for NAN bootstrap request/response frames. + * + * @param cb Bootstrap callback, or NULL to clear. + * + * @return + * - ESP_OK: succeed + */ +esp_err_t esp_nan_app_set_bootstrap_cb(esp_nan_app_bootstrap_cb_t cb); + /** * @brief NAN Pairing Bootstrapping status values */ @@ -141,7 +180,7 @@ esp_err_t esp_wifi_nan_bootstrap_request(wifi_nan_pairing_bootstrap_req_t *req); * @brief Respond to a NAN Pairing Bootstrapping request from a peer * * @attention This API should be called by the Publisher after receiving a - * WIFI_EVENT_NAN_BOOTSTRAP_INDICATION event. + * bootstrap indication via the registered NAN bootstrap callback. * * @param resp Pairing bootstrapping response parameters. * @@ -185,6 +224,24 @@ struct nan_pasn_data *esp_nan_app_get_pasn_data(void); */ void esp_nan_app_set_pasn_data(struct nan_pasn_data *pd); +/** + * @brief Clear NM-TK and ND-TK in firmware and host state. + * + * @param peer_nmi Peer NMI (6 octets), or NULL to clear all peers on + * @p service_id. + * @param service_id Own service id; used only when @p peer_nmi is NULL. + */ +void esp_nan_app_clear_peer_tks(const uint8_t *peer_nmi, uint8_t service_id); + +/** + * @brief Terminate all active NDPs with a peer (PASN verify prep). + * + * @param peer_nmi Peer NMI (6 octets). + * + * @return ESP_OK if all ends succeeded or none were active. + */ +esp_err_t esp_nan_app_end_peer_datapaths(const uint8_t *peer_nmi); + #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ #ifdef __cplusplus diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c index 9b4386fe7f8..885ea6dcc58 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c @@ -5,6 +5,7 @@ */ #include +#include #include "esp_wifi.h" #include "esp_private/wifi.h" #include "esp_wifi_netif.h" @@ -43,6 +44,18 @@ bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_at (void)nira_attr_len; return false; } + +bool esp_nan_verify_nira_get_own_svc(uint8_t *peer_mac, uint8_t *nira_attr, + uint16_t nira_attr_len, uint8_t *own_inst_id) +{ + (void)peer_mac; + (void)nira_attr; + (void)nira_attr_len; + if (own_inst_id) { + *own_inst_id = 0; + } + return false; +} #endif #if defined(CONFIG_ESP_WIFI_NAN_SYNC_ENABLE) && defined(CONFIG_ESP_WIFI_PASN_SUPPORT) @@ -220,6 +233,103 @@ void nan_app_clear_paired_peers(void) } #endif /* CONFIG_ESP_WIFI_NAN_SECURITY */ +static void nan_app_clear_one_peer_tks(const uint8_t *peer_nmi) +{ + uint8_t key_rsc[8] = {0}; + static const uint8_t zero_mac[6] = {0}; + + if (!peer_nmi || memcmp(peer_nmi, zero_mac, 6) == 0) { + return; + } + + NAN_DATA_LOCK(); + + /* NM-TK is bound to peer NMI (see nan_pasn_install_nan_pairwise_tk). */ + esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_CCMP, + (uint8_t *)peer_nmi, 1, 1, + key_rsc, sizeof(key_rsc), + NULL, 0, NAN_KEY_NM_TK); + +#ifdef CONFIG_ESP_WIFI_NAN_SECURITY + struct ndl_info *ndl = nan_find_ndl(0, (uint8_t *)peer_nmi); + uint8_t *key_addr = (uint8_t *)peer_nmi; + + if (ndl) { + if (memcmp(ndl->peer_ndi, zero_mac, 6) != 0) { + key_addr = ndl->peer_ndi; + } + + esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_CCMP, + key_addr, 0, 1, + key_rsc, sizeof(key_rsc), + NULL, 0, NAN_KEY_ND_TK); + + forced_memzero(ndl->nd_tk, sizeof(ndl->nd_tk)); + forced_memzero(ndl->nd_kck, sizeof(ndl->nd_kck)); + forced_memzero(ndl->nd_kek, sizeof(ndl->nd_kek)); + ndl->ptk_set = 0; + ndl->tk_len = 0; + ndl->kck_len = 0; + ndl->kek_len = 0; + } + + /* Fallback when no NDL slot tracks peer_ndi yet. */ + esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_CCMP, + (uint8_t *)peer_nmi, 0, 1, + key_rsc, sizeof(key_rsc), + NULL, 0, NAN_KEY_ND_TK); +#endif /* CONFIG_ESP_WIFI_NAN_SECURITY */ + + NAN_DATA_UNLOCK(); +} + +void esp_nan_app_clear_peer_tks(const uint8_t *peer_nmi, uint8_t service_id) +{ + if (peer_nmi) { + nan_app_clear_one_peer_tks(peer_nmi); + return; + } + + if (service_id == 0) { + return; + } + +#ifdef CONFIG_ESP_WIFI_NAN_SYNC_ENABLE + uint8_t peer_nmis[NAN_MAX_PEERS_RECORD][MACADDR_LEN]; + int peer_count = 0; + struct own_svc_info *p_own_svc; + struct peer_svc_info *temp; + + NAN_DATA_LOCK(); + p_own_svc = nan_find_own_svc(service_id); + if (!p_own_svc) { + NAN_DATA_UNLOCK(); + return; + } + SLIST_FOREACH(temp, &(p_own_svc->peer_list), next) { + bool dup = false; + + for (int i = 0; i < peer_count; i++) { + if (MACADDR_EQUAL(peer_nmis[i], temp->peer_nmi)) { + dup = true; + break; + } + } + if (!dup && peer_count < NAN_MAX_PEERS_RECORD) { + MACADDR_COPY(peer_nmis[peer_count], temp->peer_nmi); + peer_count++; + } + } + NAN_DATA_UNLOCK(); + + for (int i = 0; i < peer_count; i++) { + nan_app_clear_one_peer_tks(peer_nmis[i]); + } +#else + (void)service_id; +#endif /* CONFIG_ESP_WIFI_NAN_SYNC_ENABLE */ +} + #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ void esp_wifi_nan_get_ipv6_linklocal_from_mac(ip6_addr_t *ip6, uint8_t *mac_addr) @@ -496,9 +606,12 @@ static struct own_svc_info *nan_claim_own_svc_slot(uint8_t type, const char svc_ if (pairing) { memcpy(&p_svc->pairing, pairing, sizeof(*pairing)); } +#else + (void)pairing; #endif #else (void)security_cfg; + (void)pairing; #endif return p_svc; } @@ -843,6 +956,9 @@ static void nan_app_service_match_cb(uint8_t sub_id, struct nan_cb_peer_info *pe #ifdef CONFIG_ESP_WIFI_NAN_PAIRING evt->bootstrapping_methods = nan_app_parse_npba_from_publish(npba); + if (peer_info->nira_verified) { + evt->already_paired = 1; + } #endif evt->ssi_version = ssi_ver; @@ -869,13 +985,31 @@ static void nan_app_replied_cb(uint8_t pub_id, struct nan_cb_peer_info *peer_inf } uint8_t sub_id = peer_info->peer_svc_id; uint8_t *sub_nmi = peer_info->peer_mac; + uint8_t *ssi = peer_info->ssi; uint16_t ssi_len = peer_info->ssi_len; uint32_t device_caps = peer_info->device_caps; NAN_DATA_LOCK(); - if (!nan_find_peer_svc(pub_id, sub_id, sub_nmi)) { + struct peer_svc_info *p_peer_svc = nan_find_peer_svc(pub_id, sub_id, sub_nmi); + if (!p_peer_svc) { + p_peer_svc = nan_find_peer_svc(pub_id, 0, sub_nmi); + } + if (!p_peer_svc) { nan_record_peer_svc(pub_id, sub_id, sub_nmi, device_caps); + } else { + if (p_peer_svc->svc_id != sub_id) { + p_peer_svc->svc_id = sub_id; + } + if (p_peer_svc->own_svc_id != pub_id) { + p_peer_svc->own_svc_id = pub_id; + } + if (p_peer_svc->device_caps != device_caps) { + p_peer_svc->device_caps = device_caps; + } + if (!MACADDR_EQUAL(p_peer_svc->peer_nmi, sub_nmi)) { + MACADDR_COPY(p_peer_svc->peer_nmi, sub_nmi); + } } NAN_DATA_UNLOCK(); @@ -915,8 +1049,25 @@ static void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_inf uint32_t device_caps = peer_info->device_caps; NAN_DATA_LOCK(); - if (!nan_find_peer_svc(svc_id, peer_svc_id, peer_mac)) { + struct peer_svc_info *p_peer_svc = nan_find_peer_svc(svc_id, peer_svc_id, peer_mac); + if (!p_peer_svc) { + p_peer_svc = nan_find_peer_svc(svc_id, 0, peer_mac); + } + if (!p_peer_svc) { nan_record_peer_svc(svc_id, peer_svc_id, peer_mac, device_caps); + } else { + if (p_peer_svc->svc_id != peer_svc_id) { + p_peer_svc->svc_id = peer_svc_id; + } + if (p_peer_svc->own_svc_id != svc_id) { + p_peer_svc->own_svc_id = svc_id; + } + if (p_peer_svc->device_caps != device_caps) { + p_peer_svc->device_caps = device_caps; + } + if (!MACADDR_EQUAL(p_peer_svc->peer_nmi, peer_mac)) { + MACADDR_COPY(p_peer_svc->peer_nmi, peer_mac); + } } NAN_DATA_UNLOCK(); @@ -931,6 +1082,7 @@ static void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_inf #if defined(CONFIG_ESP_WIFI_NAN_PAIRING) if (npba) { nan_app_parse_npba_from_receive(svc_id, peer_svc_id, peer_mac, npba); + return; } #endif @@ -999,8 +1151,8 @@ static void nan_app_ndp_indication_cb(uint8_t pub_id, struct ndp_cb_peer_info *p nan_record_peer_svc(pub_id, 0, peer_nmi, device_caps); } - struct ndl_info *ndl = nan_find_ndl(ndp_id, (uint8_t *)peer_nmi); - if (ndl && peer_ndi) { + struct ndl_info *ndl = nan_find_ndl(ndp_id, peer_nmi); + if (ndl) { MACADDR_COPY(ndl->peer_ndi, peer_ndi); } @@ -1311,6 +1463,7 @@ static void nan_action_txdone_cb(uint32_t context, bool tx_status) static void esp_nan_ndp_tx_done_cb(uint8_t ndp_id, const uint8_t *peer_nmi, uint8_t msg_type, bool tx_status) { + NAN_DATA_LOCK(); struct ndl_info *ndl = nan_find_ndl(ndp_id, (uint8_t *)peer_nmi); @@ -1440,6 +1593,71 @@ void esp_nan_app_init(void) esp_nan_internal_register_secure_dp_funcs(&s_nan_secure_dp_funcs); } +#ifdef CONFIG_ESP_WIFI_NAN_PAIRING +#if defined(CONFIG_ESP_WIFI_NAN_SECURITY) +static bool nan_peer_cred_npk_present(const wifi_nan_peer_creds_t *c) +{ + static const uint8_t zero_npk[ESP_WIFI_NAN_NPK_LEN] = {0}; + + if (!c || !c->is_valid) { + return false; + } + return memcmp(c->npk, zero_npk, ESP_WIFI_NAN_NPK_LEN) != 0; +} +#endif + +static bool nan_peer_nik_cached_cb(uint8_t *peer_mac) +{ + bool cached = false; + struct peer_svc_info *peer; + + if (!peer_mac) { + return false; + } + + NAN_DATA_LOCK(); + peer = nan_find_peer_svc(0, 0, peer_mac); +#if defined(CONFIG_ESP_WIFI_NAN_SECURITY) + if (peer) { + struct own_svc_info *own = nan_find_own_svc(peer->own_svc_id); + + 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_peer_cred_npk_present(&s_nan_ctx.peer_creds[i])) { + continue; + } + if (own && memcmp(s_nan_ctx.peer_creds[i].service_hash, own->svc_hash, 6) == 0) { + cached = true; + break; + } + } + } + if (!cached) { + uint8_t npk_slots = 0; + + for (uint8_t i = 0; i < s_nan_ctx.num_peer_creds; i++) { + if (s_nan_ctx.peer_creds[i].is_valid && + nan_peer_cred_npk_present(&s_nan_ctx.peer_creds[i])) { + npk_slots++; + } + } + if (npk_slots == 1) { + cached = true; + } + } +#else + if (peer && peer->has_nik) { + cached = true; + } +#endif + NAN_DATA_UNLOCK(); + + return cached; +} +#endif + void esp_nan_action_start(esp_netif_t *nan_netif) { nan_set_app_default_handlers(); @@ -1462,12 +1680,14 @@ void esp_nan_action_start(esp_netif_t *nan_netif) .get_nira_len = esp_nan_get_nira_len, .construct_nira = esp_nan_construct_nira, .verify_nira = esp_nan_verify_nira, + .peer_nik_cached = nan_peer_nik_cached_cb, .receive_pasn = handle_auth_pasn, #endif }; esp_nan_internal_register_callbacks(&nan_cb); ESP_LOGI(TAG, "NAN Discovery started."); + os_event_group_clear_bits(nan_event_group, NAN_STOPPED_BIT); os_event_group_set_bits(nan_event_group, NAN_STARTED_BIT); } @@ -1494,6 +1714,7 @@ void esp_nan_action_stop(void) #endif esp_nan_internal_register_callbacks(NULL); + os_event_group_clear_bits(nan_event_group, NAN_STARTED_BIT); os_event_group_set_bits(nan_event_group, NAN_STOPPED_BIT); } @@ -1516,7 +1737,6 @@ esp_err_t esp_wifi_nan_sync_start(const wifi_nan_sync_config_t *nan_cfg) return ret; } - /* XXX: For now, NAN-USD and NAN-Sync can not coexist. */ /* NAN-Synchronization Only */ wifi_config_t config = {0}; @@ -1536,10 +1756,16 @@ esp_err_t esp_wifi_nan_sync_start(const wifi_nan_sync_config_t *nan_cfg) 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; + s_nan_ctx.nik_lifetime = 0; if (nan_cfg->reset_current_nvs_creds) { /* Start from a clean slate: drop every credential persisted in NVS. */ - esp_wifi_nan_erase_all_creds(); + ret = esp_wifi_nan_erase_all_creds(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase NAN credentials from NVS"); + NAN_DATA_UNLOCK(); + return ret; + } } 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"); @@ -1557,7 +1783,12 @@ esp_err_t esp_wifi_nan_sync_start(const wifi_nan_sync_config_t *nan_cfg) /* 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); + ret = esp_wifi_nan_save_own_nik(s_nan_ctx.own_nik); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to persist own NIK to NVS"); + NAN_DATA_UNLOCK(); + return ret; + } } } /* Drop the cached NIRA tag; it was derived from the previous NIK. */ @@ -1570,6 +1801,7 @@ esp_err_t esp_wifi_nan_sync_start(const wifi_nan_sync_config_t *nan_cfg) memcpy(&config.nan, nan_cfg, sizeof(wifi_nan_sync_config_t)); ESP_RETURN_ON_ERROR(esp_wifi_set_config(WIFI_IF_NAN, &config), TAG, "Setting NAN config failed"); + os_event_group_clear_bits(nan_event_group, NAN_STARTED_BIT); if (esp_wifi_start() != ESP_OK) { ESP_LOGE(TAG, "Starting wifi failed"); NAN_DATA_LOCK(); @@ -1616,6 +1848,8 @@ esp_err_t esp_wifi_nan_sync_stop(void) NAN_DATA_UNLOCK(); } + /* Wait for a fresh stop event, not a stale bit from prior run. */ + os_event_group_clear_bits(nan_event_group, NAN_STOPPED_BIT); ESP_RETURN_ON_ERROR(esp_wifi_stop(), TAG, "Stopping NAN failed"); EventBits_t bits = os_event_group_wait_bits(nan_event_group, NAN_STOPPED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); @@ -1788,7 +2022,7 @@ uint8_t esp_wifi_nan_publish_service(const wifi_nan_publish_cfg_t *publish_cfg) } #ifdef CONFIG_ESP_WIFI_NAN_PAIRING - if (nan_check_paired_service_hash(service_id)) { + if (cfg->pairing && nan_check_paired_service_hash(service_id)) { cfg->pairing->pairing_setup = false; } #endif @@ -1894,6 +2128,7 @@ uint8_t esp_wifi_nan_subscribe_service(const wifi_nan_subscribe_cfg_t *subscribe #endif /* CONFIG_ESP_WIFI_NAN_USD_ENABLE */ #ifdef CONFIG_ESP_WIFI_NAN_SYNC_ENABLE + wifi_nan_subscribe_cfg_t *cfg = NULL; uint8_t service_id[6] = {0}; if (subscribe_cfg->security_reqd) { @@ -1940,38 +2175,64 @@ uint8_t esp_wifi_nan_subscribe_service(const wifi_nan_subscribe_cfg_t *subscribe goto fail; } + cfg = os_zalloc(sizeof(*cfg)); + if (!cfg) { + ESP_LOGE(TAG, "Failed to allocate subscribe config"); + goto fail; + } + memcpy(cfg, subscribe_cfg, sizeof(*cfg)); + cfg->pairing = NULL; + if (subscribe_cfg->pairing) { + cfg->pairing = os_malloc(sizeof(*cfg->pairing)); + if (!cfg->pairing) { + ESP_LOGE(TAG, "Failed to copy pairing config"); + goto fail; + } + memcpy(cfg->pairing, subscribe_cfg->pairing, sizeof(*cfg->pairing)); + } + /* Pre-claim host slot BEFORE the blob's subscribe call; see comment on * the publish path for the watchdog rationale. */ - if (!nan_compute_service_id(subscribe_cfg->service_name, service_id)) { - ESP_LOGE(TAG, "Failed to compute Service ID for %s", subscribe_cfg->service_name); + if (!nan_compute_service_id(cfg->service_name, service_id)) { + ESP_LOGE(TAG, "Failed to compute Service ID for %s", cfg->service_name); goto fail; } #ifdef CONFIG_ESP_WIFI_NAN_PAIRING - if (nan_check_paired_service_hash(service_id)) { - subscribe_cfg->pairing->pairing_setup = false; + if (cfg->pairing && nan_check_paired_service_hash(service_id)) { + cfg->pairing->pairing_setup = false; } #endif - if (!nan_claim_own_svc_slot(ESP_NAN_SUBSCRIBE, subscribe_cfg->service_name, - subscribe_cfg->security_cfg, subscribe_cfg->pairing)) { + if (!nan_claim_own_svc_slot(ESP_NAN_SUBSCRIBE, cfg->service_name, + cfg->security_cfg, cfg->pairing)) { ESP_LOGE(TAG, "No free service slot"); goto fail; } - if (esp_nan_internal_subscribe_service(subscribe_cfg, (uint8_t *) &sub_id, false) != ESP_OK) { - ESP_LOGE(TAG, "Failed to subscribe to service '%s'", subscribe_cfg->service_name); - nan_abort_own_svc(subscribe_cfg->service_name); + if (esp_nan_internal_subscribe_service(cfg, (uint8_t *) &sub_id, false) != ESP_OK) { + ESP_LOGE(TAG, "Failed to subscribe to service '%s'", cfg->service_name); + nan_abort_own_svc(cfg->service_name); goto fail; } - ESP_LOGI(TAG, "Started Subscribing to %s [Service ID - %u]", subscribe_cfg->service_name, sub_id); - nan_finalize_own_svc(subscribe_cfg->service_name, (uint8_t) sub_id, false, service_id); + ESP_LOGI(TAG, "Started Subscribing to %s [Service ID - %u]", cfg->service_name, sub_id); + nan_finalize_own_svc(cfg->service_name, (uint8_t) sub_id, false, service_id); + if (cfg->pairing) { + os_free(cfg->pairing); + } + os_free(cfg); NAN_DATA_UNLOCK(); return sub_id; fail: + if (cfg) { + if (cfg->pairing) { + os_free(cfg->pairing); + } + os_free(cfg); + } NAN_DATA_UNLOCK(); return 0; #endif /* CONFIG_ESP_WIFI_NAN_SYNC_ENABLE */ @@ -2072,6 +2333,12 @@ esp_err_t esp_wifi_nan_cancel_service(uint8_t service_id) } #endif /* CONFIG_ESP_WIFI_NAN_USD_ENABLE */ #ifdef CONFIG_ESP_WIFI_NAN_SYNC_ENABLE +#if CONFIG_ESP_WIFI_NAN_PAIRING + /* Snapshot peer NMIs before cancel; clear TKs only after a successful cancel + * so a failed attempt does not leave an active service without keys. */ + uint8_t peer_nmis[NAN_MAX_PEERS_RECORD][MACADDR_LEN]; + int peer_count = 0; +#endif NAN_DATA_LOCK(); struct own_svc_info *p_own_svc = nan_find_own_svc(service_id); @@ -2080,6 +2347,27 @@ esp_err_t esp_wifi_nan_cancel_service(uint8_t service_id) goto fail; } +#if CONFIG_ESP_WIFI_NAN_PAIRING + { + struct peer_svc_info *temp; + + SLIST_FOREACH(temp, &(p_own_svc->peer_list), next) { + bool dup = false; + + for (int i = 0; i < peer_count; i++) { + if (MACADDR_EQUAL(peer_nmis[i], temp->peer_nmi)) { + dup = true; + break; + } + } + if (!dup && peer_count < NAN_MAX_PEERS_RECORD) { + MACADDR_COPY(peer_nmis[peer_count], temp->peer_nmi); + peer_count++; + } + } + } +#endif + if (p_own_svc->type == ESP_NAN_PUBLISH) { if (esp_nan_internal_publish_service(NULL, &service_id, true) == ESP_OK) { nan_reset_service(service_id, false); @@ -2102,18 +2390,24 @@ fail: done: NAN_DATA_UNLOCK(); +#if CONFIG_ESP_WIFI_NAN_PAIRING + /* Cancel succeeded; now safe to wipe pairwise keys for the collected peers. */ + for (int i = 0; i < peer_count; i++) { + nan_app_clear_one_peer_tks(peer_nmis[i]); + } +#endif return ESP_OK; #endif /* CONFIG_ESP_WIFI_NAN_SYNC_ENABLE */ return ESP_FAIL; } - #ifdef CONFIG_ESP_WIFI_NAN_SYNC_ENABLE uint8_t esp_wifi_nan_datapath_req(wifi_nan_datapath_req_t *req) { uint8_t ndp_id = 0; uint8_t own_bssid[6]; ip_addr_t own_ipv6 = {0}; + NAN_DATA_LOCK(); struct peer_svc_info *p_peer_svc = nan_find_peer_svc(0, req->pub_id, req->peer_mac); @@ -2303,6 +2597,45 @@ esp_err_t esp_wifi_nan_datapath_end(wifi_nan_datapath_end_req_t *req) return ESP_OK; } +#ifdef CONFIG_ESP_WIFI_NAN_PAIRING +esp_err_t esp_nan_app_end_peer_datapaths(const uint8_t *peer_nmi) +{ + wifi_nan_datapath_end_req_t ndp_end[ESP_WIFI_NAN_DATAPATH_MAX_PEERS]; + int count = 0; + esp_err_t last_err = ESP_OK; + + if (!peer_nmi) { + return ESP_ERR_INVALID_ARG; + } + + NAN_DATA_LOCK(); + for (int i = 0; i < ESP_WIFI_NAN_DATAPATH_MAX_PEERS; i++) { + struct ndl_info *ndl = &s_nan_ctx.ndl[i]; + + if (ndl->ndp_id != 0 && MACADDR_EQUAL(ndl->peer_nmi, peer_nmi)) { + ndp_end[count].ndp_id = ndl->ndp_id; + MACADDR_COPY(ndp_end[count].peer_mac, peer_nmi); + count++; + } + } + NAN_DATA_UNLOCK(); + + if (count == 0) { + return ESP_OK; + } + + for (int i = 0; i < count; i++) { + esp_err_t ret = esp_wifi_nan_datapath_end(&ndp_end[i]); + + if (ret != ESP_OK) { + last_err = ret; + } + } + + return last_err; +} +#endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ + esp_err_t esp_wifi_nan_get_own_svc_info(uint8_t *own_svc_id, char *svc_name, int *num_peer_records) { struct own_svc_info *own_svc = NULL; 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 414713635d5..c7ee0cd8a5a 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 @@ -231,6 +231,11 @@ struct own_svc_info { #if CONFIG_ESP_WIFI_NAN_PAIRING bool nik_fup_pending; uint8_t nik_fup_pending_peer_nmi[MACADDR_LEN]; + /* Set when the current PASN session for @c verify_session_peer_nmi is a + * pairing verification (re-pair). Consumed once in the key-installed + * callback to skip the NIK follow-up exchange. */ + bool verify_session_pending; + uint8_t verify_session_peer_nmi[MACADDR_LEN]; #endif uint8_t svc_hash[6]; }; @@ -308,6 +313,7 @@ typedef struct { wifi_nan_peer_creds_t peer_creds[ESP_WIFI_NAN_MAX_PEER_CREDS]; uint8_t num_peer_creds; bool use_nvs_for_caching; + uint32_t nik_lifetime; #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 fcca2e10ec1..2937bec29b7 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 @@ -19,19 +19,187 @@ #include "esp_mac.h" #include "esp_nan.h" #include "nan_i.h" +#include "apps_private/wifi_apps_private.h" #include "os.h" #include "utils/common.h" #include "utils/eloop.h" +#include "esp_wifi_driver.h" + +#if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) #include "esp_private/esp_supp_nan.h" -#include "apps_private/wifi_apps_private.h" +#include "common/defs.h" +#endif static const char *TAG = "nan_pairing"; -/* Default NIK / pairing-record lifetime (also reused for paired-peer cache - * entries) — matches the NIK Key Lifetime KDE we transmit in the post-pairing - * Shared Key Descriptor (Wi-Fi Aware v4.0 §7.6.4.2). */ -#define NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC 86400U +static esp_nan_app_bootstrap_cb_t s_bootstrap_cb; + +/* Consume (clear and return) any pending verify-session for @a peer_nmi. + * The flag lives on the own service that the peer was verified against, so it + * survives even when no peer_svc_info exists for a MAC-randomised peer. */ +static bool nan_pairing_take_verify_session(const uint8_t *peer_nmi) +{ + bool pending = false; + + if (!peer_nmi) { + return false; + } + + NAN_DATA_LOCK(); + for (int i = 0; i < ESP_WIFI_NAN_MAX_SVC_SUPPORTED; i++) { + struct own_svc_info *own = &s_nan_ctx.own_svc[i]; + + if (own->verify_session_pending && + MACADDR_EQUAL(own->verify_session_peer_nmi, peer_nmi)) { + own->verify_session_pending = false; + pending = true; + break; + } + } + NAN_DATA_UNLOCK(); + + return pending; +} + +void esp_nan_pairing_mark_verify_session(uint8_t own_inst_id, const uint8_t *peer_nmi) +{ + struct own_svc_info *own; + + if (!own_inst_id || !peer_nmi) { + return; + } + + NAN_DATA_LOCK(); + own = nan_find_own_svc(own_inst_id); + if (own) { + own->verify_session_pending = true; + MACADDR_COPY(own->verify_session_peer_nmi, peer_nmi); + } + NAN_DATA_UNLOCK(); +} + +void esp_nan_pairing_clear_verify_session(const uint8_t *peer_nmi) +{ + if (!peer_nmi) { + return; + } + + NAN_DATA_LOCK(); + for (int i = 0; i < ESP_WIFI_NAN_MAX_SVC_SUPPORTED; i++) { + struct own_svc_info *own = &s_nan_ctx.own_svc[i]; + + if (own->verify_session_pending && + MACADDR_EQUAL(own->verify_session_peer_nmi, peer_nmi)) { + own->verify_session_pending = false; + } + } + NAN_DATA_UNLOCK(); +} + +/* Resolve the own service instance id a peer is associated with, used to anchor + * a proactive verify-session flag. Returns 0 when no peer record exists yet. */ +static uint8_t nan_pairing_resolve_own_inst(uint8_t peer_svc_id, const uint8_t *peer_nmi) +{ + struct peer_svc_info *peer; + uint8_t own_inst_id = 0; + + if (!peer_nmi) { + return 0; + } + + NAN_DATA_LOCK(); + peer = nan_find_peer_svc(0, peer_svc_id, (uint8_t *)peer_nmi); + if (peer) { + own_inst_id = peer->own_svc_id; + } + NAN_DATA_UNLOCK(); + + return own_inst_id; +} + +esp_err_t esp_nan_app_set_bootstrap_cb(esp_nan_app_bootstrap_cb_t cb) +{ + s_bootstrap_cb = cb; + return ESP_OK; +} + +static void nan_app_bootstrap_notify(const wifi_nan_bootstrap_event_t *evt) +{ + if (!s_bootstrap_cb) { + ESP_LOGW(TAG, "Bootstrap frame received but no callback registered"); + return; + } + s_bootstrap_cb(evt); +} + +static bool nan_peer_cred_npk_present(const wifi_nan_peer_creds_t *c) +{ + static const uint8_t zero_npk[ESP_WIFI_NAN_NPK_LEN] = {0}; + + if (!c || !c->is_valid) { + return false; + } + return memcmp(c->npk, zero_npk, ESP_WIFI_NAN_NPK_LEN) != 0; +} + +/* Resolve NPK from s_nan_ctx.peer_creds. Caller holds NAN_DATA_LOCK. */ +static const wifi_nan_peer_creds_t *nan_peer_cred_lookup(void) +{ + const wifi_nan_peer_creds_t *only = NULL; + uint8_t npk_slots = 0; + + for (int idx = 0; idx < ESP_WIFI_NAN_MAX_SVC_SUPPORTED; idx++) { + const struct own_svc_info *own = &s_nan_ctx.own_svc[idx]; + + if (own->svc_id == 0) { + continue; + } + for (uint8_t i = 0; i < s_nan_ctx.num_peer_creds; i++) { + if (s_nan_ctx.peer_creds[i].is_valid && + memcmp(s_nan_ctx.peer_creds[i].service_hash, own->svc_hash, 6) == 0 && + nan_peer_cred_npk_present(&s_nan_ctx.peer_creds[i])) { + return &s_nan_ctx.peer_creds[i]; + } + } + } + + for (uint8_t i = 0; i < s_nan_ctx.num_peer_creds; i++) { + if (!nan_peer_cred_npk_present(&s_nan_ctx.peer_creds[i])) { + continue; + } + only = &s_nan_ctx.peer_creds[i]; + npk_slots++; + } + if (npk_slots == 1) { + return only; + } + + return NULL; +} + +int nan_global_peer_npk_lookup(uint8_t *npk, size_t *npk_len, int *akmp) +{ + const wifi_nan_peer_creds_t *slot = NULL; + + if (!npk || !npk_len || !akmp) { + return -1; + } + + NAN_DATA_LOCK(); + slot = nan_peer_cred_lookup(); + if (slot) { + memcpy(npk, slot->npk, ESP_WIFI_NAN_NPK_LEN); + *npk_len = ESP_WIFI_NAN_NPK_LEN; + /* Wi-Fi Aware pairing: PASN frames use SAE as the single base AKM. */ + *akmp = WPA_KEY_MGMT_SAE; + NAN_DATA_UNLOCK(); + return 0; + } + NAN_DATA_UNLOCK(); + + return -1; +} struct nan_pasn_data *esp_nan_app_get_pasn_data(void) { @@ -43,13 +211,6 @@ void esp_nan_app_set_pasn_data(struct nan_pasn_data *pd) s_nan_ctx.nan_pasn_data = pd; } -static void nan_pairing_key_installed_cb(const uint8_t *peer_nmi, - uint8_t role, - uint8_t ndp_csid, - const uint8_t *nd_pmk, - size_t nd_pmk_len, - uint32_t nik_lifetime_sec); - bool nan_pairing_validate_publish_bootstrapping(uint16_t bootstrapping_methods) { if (!bootstrapping_methods) { @@ -79,34 +240,42 @@ uint16_t nan_app_parse_npba_from_publish(const struct nan_cb_npba_t *npba) void nan_app_bootstrap_indication(uint8_t peer_svc_id, uint8_t pub_id, uint8_t peer_nmi[6], uint16_t selected_method) { + wifi_nan_bootstrap_event_t evt = {0}; + ESP_LOGI(TAG, "Pairing Bootstrapping Request from "MACSTR" [pub_id=%d, method=0x%x]", MAC2STR(peer_nmi), pub_id, selected_method); - wifi_event_nan_bootstrap_indication_t evt = {0}; + evt.type = WIFI_NAN_NPBA_TYPE_REQUEST; evt.peer_svc_id = peer_svc_id; evt.own_svc_id = pub_id; MACADDR_COPY(evt.peer_nmi, peer_nmi); - evt.selected_method = selected_method; + evt.methods = selected_method; - nan_app_post_event(WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, &evt, sizeof(evt)); + nan_app_bootstrap_notify(&evt); } void nan_app_bootstrap_completed(uint8_t status, uint8_t peer_svc_id, uint8_t sub_id, uint8_t peer_nmi[6], uint16_t matched_method, uint8_t reason_code) { + wifi_nan_bootstrap_event_t evt = {0}; + ESP_LOGI(TAG, "Pairing Bootstrapping Response from "MACSTR" [sub_id=%d, status=%d, method=0x%x]", MAC2STR(peer_nmi), sub_id, status, matched_method); - wifi_event_nan_bootstrap_complete_t evt = {0}; + if (peer_nmi == NULL) { + return; + } + + evt.type = WIFI_NAN_NPBA_TYPE_RESPONSE; evt.status = status; evt.peer_svc_id = peer_svc_id; evt.own_svc_id = sub_id; MACADDR_COPY(evt.peer_nmi, peer_nmi); - evt.matched_method = matched_method; + evt.methods = matched_method; evt.reason_code = reason_code; - nan_app_post_event(WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, &evt, sizeof(evt)); + nan_app_bootstrap_notify(&evt); } bool nan_app_parse_npba_from_receive(uint8_t own_svc_id, uint8_t peer_svc_id, @@ -243,48 +412,6 @@ esp_err_t esp_wifi_nan_bootstrap_response(wifi_nan_pairing_bootstrapping_resp_t return ret; } -esp_err_t esp_wifi_nan_pairing_start(wifi_nan_pairing_config_t *cfg) -{ - if (!cfg) { - ESP_LOGE(TAG, "Pairing config NULL"); - return ESP_ERR_INVALID_ARG; - } - - if (cfg->cred.pincode != UINT32_MAX && - cfg->cred.pincode > NAN_PAIRING_PINCODE_MAX) { - ESP_LOGE(TAG, "Invalid pincode %u (valid range %u..%u or UINT32_MAX for default)", - cfg->cred.pincode, NAN_PAIRING_PINCODE_MIN, NAN_PAIRING_PINCODE_MAX); - return ESP_ERR_INVALID_ARG; - } - - int ret; - - switch (cfg->self_role) { - case NAN_PAIRING_ROLE_RESPONDER: - ret = esp_nan_supp_pasn_responder_init(cfg->peer_nmi, cfg->cred.pincode, - NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC, - nan_pairing_key_installed_cb); - if (ret != 0) { - ESP_LOGE(TAG, "NAN PASN responder init failed for "MACSTR, MAC2STR(cfg->peer_nmi)); - return ESP_FAIL; - } - break; - case NAN_PAIRING_ROLE_INITIATOR: - ret = esp_nan_supp_pasn_initiator_auth(cfg->peer_nmi, cfg->cred.pincode, - NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC, - nan_pairing_key_installed_cb); - if (ret != 0) { - ESP_LOGE(TAG, "NAN PASN initiator auth failed for "MACSTR, MAC2STR(cfg->peer_nmi)); - return ESP_FAIL; - } - break; - default: - ESP_LOGE(TAG, "Invalid pairing role %d", cfg->self_role); - return ESP_ERR_INVALID_ARG; - } - return ESP_OK; -} - /* NIRA: ID(1) + Len(2) + CipherVersion(1) + Nonce(8) + Tag(8) = 20 */ #define NAN_ATTR_ID_IDENTITY_RESOLUTION 0x2B #define NAN_NIRA_NONCE_LEN 8 @@ -299,47 +426,6 @@ uint32_t esp_nan_get_nira_len(void) return NAN_NIRA_ATTR_LEN; } -/** - * Derive a NIRA tag for cipher version 0 (Wi-Fi Aware v4.0): - * Tag = Truncate-64(HMAC-SHA-256(NIK, "NIR" || NMI || Nonce)) - * - * Ported from hostap @c nan_crypto_derive_nira_tag (src/nan/nan_crypto.c), - * adapted to the ESP-IDF crypto trampoline. - */ -static int nan_pairing_derive_nira_tag(const uint8_t nik[NAN_PASN_NIK_LEN], - const uint8_t nmi_addr[ETH_ALEN], - const uint8_t nira_nonce[NAN_NIRA_NONCE_LEN], - uint8_t tag_out[NAN_NIRA_TAG_LEN]) -{ - const unsigned char *addr[3]; - int len_arr[3]; - uint8_t digest[32]; - - if (!nik || !nmi_addr || !nira_nonce || !tag_out) { - return -1; - } - if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) { - return -1; - } - - addr[0] = (const unsigned char *)NAN_NIRA_STR; - len_arr[0] = NAN_NIRA_STR_LEN; - addr[1] = nmi_addr; - len_arr[1] = ETH_ALEN; - addr[2] = nira_nonce; - len_arr[2] = NAN_NIRA_NONCE_LEN; - - if (g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(nik, NAN_PASN_NIK_LEN, - 3, addr, len_arr, - digest) != 0) { - return -1; - } - - memcpy(tag_out, digest, NAN_NIRA_TAG_LEN); - memset(digest, 0, sizeof(digest)); - return 0; -} - int esp_nan_construct_nira(uint8_t *frm) { const uint8_t *nonce; @@ -356,11 +442,18 @@ int esp_nan_construct_nira(uint8_t *frm) tag = s_nan_ctx.cached_nira_tag; } else { uint8_t own_nmi[MACADDR_LEN]; + const unsigned char *addr[3]; + int len_arr[3]; + uint8_t digest[32]; if (!s_nan_ctx.own_nik_valid) { ESP_LOGW(TAG, "NIRA: own NIK is not available"); return 0; } + if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) { + ESP_LOGE(TAG, "NIRA: hmac_sha256_vector not registered"); + return 0; + } if (esp_wifi_get_mac(WIFI_IF_NAN, own_nmi) != ESP_OK) { ESP_LOGE(TAG, "NIRA: failed to read NAN NMI"); return 0; @@ -369,12 +462,23 @@ int esp_nan_construct_nira(uint8_t *frm) ESP_LOGE(TAG, "NIRA: failed to generate nonce"); return 0; } + /* Tag = Truncate-64(HMAC-SHA-256(NIK, "NIR" || NMI || Nonce)) */ - if (nan_pairing_derive_nira_tag(s_nan_ctx.own_nik, own_nmi, - fresh_nonce, fresh_tag) != 0) { + addr[0] = (const unsigned char *)NAN_NIRA_STR; + len_arr[0] = NAN_NIRA_STR_LEN; + addr[1] = own_nmi; + len_arr[1] = MACADDR_LEN; + addr[2] = fresh_nonce; + len_arr[2] = NAN_NIRA_NONCE_LEN; + if (g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(s_nan_ctx.own_nik, + ESP_WIFI_NAN_NIK_LEN, + 3, addr, len_arr, + digest) != 0) { ESP_LOGE(TAG, "NIRA: tag derivation failed"); return 0; } + memcpy(fresh_tag, digest, NAN_NIRA_TAG_LEN); + memset(digest, 0, sizeof(digest)); memcpy(s_nan_ctx.cached_nira_nonce, fresh_nonce, NAN_NIRA_NONCE_LEN); memcpy(s_nan_ctx.cached_nira_tag, fresh_tag, NAN_NIRA_TAG_LEN); @@ -405,7 +509,6 @@ int esp_nan_construct_nira(uint8_t *frm) #define NAN_PASN_KDE_OUI_TYPE_NIK 36 #define NAN_PASN_KDE_OUI_TYPE_LIFETIME 37 #define NAN_PASN_KEY_LIFETIME_NIK_BIT BIT(3) -/* NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC is defined at file scope above. */ #define NAN_ATTR_ID_SHARED_KEY_DESC 0x24 #define NAN_PAIRING_NIK_FUP_TIMEOUT_SEC 2 @@ -528,6 +631,47 @@ static size_t nan_pairing_build_srv_ssi(uint8_t *buf, size_t buf_len) return (size_t)(p - buf); } +/** + * Derive a NIRA tag for cipher version 0 (Wi-Fi Aware v4.0): + * Tag = Truncate-64(HMAC-SHA-256(NIK, "NIR" || NMI || Nonce)) + * + * Ported from hostap @c nan_crypto_derive_nira_tag (src/nan/nan_crypto.c), + * adapted to the ESP-IDF crypto trampoline. + */ +static int nan_pairing_derive_nira_tag(const uint8_t nik[NAN_PASN_NIK_LEN], + const uint8_t nmi_addr[ETH_ALEN], + const uint8_t nira_nonce[NAN_NIRA_NONCE_LEN], + uint8_t tag_out[NAN_NIRA_TAG_LEN]) +{ + const unsigned char *addr[3]; + int len_arr[3]; + uint8_t digest[32]; + + if (!nik || !nmi_addr || !nira_nonce || !tag_out) { + return -1; + } + if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) { + return -1; + } + + addr[0] = (const unsigned char *)NAN_NIRA_STR; + len_arr[0] = NAN_NIRA_STR_LEN; + addr[1] = nmi_addr; + len_arr[1] = ETH_ALEN; + addr[2] = nira_nonce; + len_arr[2] = NAN_NIRA_NONCE_LEN; + + if (g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(nik, NAN_PASN_NIK_LEN, + 3, addr, len_arr, + digest) != 0) { + return -1; + } + + memcpy(tag_out, digest, NAN_NIRA_TAG_LEN); + memset(digest, 0, sizeof(digest)); + return 0; +} + static size_t nan_pairing_build_plain_key_data(uint8_t *buf, size_t buf_len, const uint8_t nik[NAN_PASN_NIK_LEN]) { @@ -559,7 +703,7 @@ static size_t nan_pairing_build_plain_key_data(uint8_t *buf, size_t buf_len, buf[pos++] = NAN_PASN_KDE_OUI_TYPE_LIFETIME; WPA_PUT_LE16(&buf[pos], NAN_PASN_KEY_LIFETIME_NIK_BIT); pos += 2; - WPA_PUT_BE32(&buf[pos], NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC); + WPA_PUT_BE32(&buf[pos], s_nan_ctx.nik_lifetime); pos += 4; /* @@ -567,16 +711,14 @@ static size_t nan_pairing_build_plain_key_data(uint8_t *buf, size_t buf_len, * 802.11-2020 §12.7.2 specifies Key Data padding as a single 0xDD byte * followed by zeros (not a sequence of well-formed vendor IEs). */ - { - size_t pad = (8 - (pos % 8)) % 8; - if (pad) { - if (buf_len - pos < pad) { - return 0; - } - buf[pos++] = 0xDD; - while (--pad) { - buf[pos++] = 0x00; - } + size_t pad = (8 - (pos % 8)) % 8; + if (pad) { + if (buf_len - pos < pad) { + return 0; + } + buf[pos++] = 0xDD; + while (--pad) { + buf[pos++] = 0x00; } } @@ -683,9 +825,8 @@ static esp_err_t nan_app_send_pairing_followup(uint8_t svc_id, uint8_t peer_svc_ /* NIRA proves possession of the NIK we just wrapped above; iPhone uses * it to bind the NIK to the sender and won't commit the pairing record - * without it. esp_nan_construct_nira() reuses the same cached nonce/tag we - * advertise in sync discovery. */ - nira_len = (size_t)esp_nan_construct_nira(nira_attr); + * without it. */ + nira_len = esp_nan_construct_nira(nira_attr); if (nira_len == 0) { ESP_LOGW(TAG, "Pairing follow-up: NIRA attribute build failed"); return ESP_FAIL; @@ -713,113 +854,12 @@ static void nan_app_send_pairing_followup_eloop(void *eloop_data, void *user_dat os_free(ctx); } -static void nan_pairing_key_installed_cb(const uint8_t *peer_nmi, - uint8_t role, - uint8_t ndp_csid, - const uint8_t *nd_pmk, - size_t nd_pmk_len, - uint32_t nik_lifetime_sec) -{ - if (!peer_nmi) { - return; - } - - uint32_t lifetime_sec = nik_lifetime_sec ? - nik_lifetime_sec : NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC; - - /* Cache ND-PMK for future paired NDPs (Wi-Fi Aware v4.0 §7.6.4.2). */ - if (ndp_csid && nd_pmk && nd_pmk_len == ESP_WIFI_NAN_NDP_PMK_LEN) { - (void)nan_app_register_paired_peer(peer_nmi, role, ndp_csid, - nd_pmk, nd_pmk_len, - lifetime_sec); - } else { - ESP_LOGW(TAG, "Pairing complete for " MACSTR - ": ND-PMK unavailable (csid=%u nd_pmk_len=%u); " - "paired-peer cache not updated", - MAC2STR(peer_nmi), ndp_csid, (unsigned)nd_pmk_len); - } - - struct peer_svc_info *peer = nan_find_peer_svc(0, 0, (uint8_t *)peer_nmi); - struct own_svc_info *own = NULL; - - if (peer) { - own = nan_find_own_svc(peer->own_svc_id); - } - - if (own) { - if (!own->pairing.npk_nik_caching) { - wifi_event_nan_pairing_complete_t evt = {0}; - evt.status = WIFI_NAN_PAIRING_STATUS_ACCEPTED; - evt.reason_code = 0; - MACADDR_COPY(evt.peer_nmi, peer_nmi); - esp_nan_complete_pairing(own->svc_id, peer->svc_id); - nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); - return; - } - } - - if (role == NAN_ROLE_PAIRING_INITIATOR) { - struct nan_pairing_fup_ctx *ctx = os_zalloc(sizeof(*ctx)); - if (!ctx) { - ESP_LOGW(TAG, "Pairing key installed: failed to alloc fup ctx for " MACSTR, - MAC2STR(peer_nmi)); - return; - } - - NAN_DATA_LOCK(); - - if (peer) { - ctx->svc_id = peer->own_svc_id; - ctx->peer_svc_id = peer->svc_id; - } - NAN_DATA_UNLOCK(); - - /* Without a peer service entry the follow-up would carry zero service - * IDs; drop it rather than send an invalid frame. */ - if (!ctx->svc_id || !ctx->peer_svc_id) { - ESP_LOGW(TAG, "Pairing key installed: peer service not found for " MACSTR - ", skipping initiator follow-up", MAC2STR(peer_nmi)); - os_free(ctx); - return; - } - - MACADDR_COPY(ctx->peer_mac, peer_nmi); - ctx->shared_key_attr_len = 0; - - own = nan_find_own_svc(ctx->svc_id); - if (own) { - nan_pairing_arm_pending(own, peer_nmi); - } - - if (eloop_register_timeout(0, 0, nan_app_send_pairing_followup_eloop, NULL, ctx) != 0) { - ESP_LOGW(TAG, "Pairing key installed: failed to schedule initiator follow-up"); - if (own) { - nan_pairing_cancel_svc_pending(own); - } - os_free(ctx); - } - return; - } - - if (role == NAN_ROLE_PAIRING_RESPONDER) { - NAN_DATA_LOCK(); - - if (peer) { - own = nan_find_own_svc(peer->own_svc_id); - } - NAN_DATA_UNLOCK(); - - if (own) { - nan_pairing_arm_pending(own, 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, uint8_t service_hash[6]) + * stores a zeroed key; NULL @a service_hash stores zeros. 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, + const uint8_t service_hash[6]) { wifi_nan_peer_creds_t *slot = NULL; @@ -840,7 +880,11 @@ static void nan_app_update_peer_creds(const uint8_t *peer_nik, const uint8_t *np } memcpy(slot->peer_nik, peer_nik, ESP_WIFI_NAN_NIK_LEN); - memcpy(slot->service_hash, service_hash, 6); + if (service_hash) { + memcpy(slot->service_hash, service_hash, 6); + } else { + memset(slot->service_hash, 0, sizeof(slot->service_hash)); + } if (npk) { memcpy(slot->npk, npk, ESP_WIFI_NAN_NPK_LEN); } else { @@ -849,6 +893,212 @@ static void nan_app_update_peer_creds(const uint8_t *peer_nik, const uint8_t *np slot->is_valid = true; } +static void nan_pairing_post_confirm(const uint8_t *peer_nmi) +{ + wifi_event_nan_pairing_complete_t evt = {0}; + + evt.status = WIFI_NAN_PAIRING_STATUS_ACCEPTED; + MACADDR_COPY(evt.peer_nmi, peer_nmi); + nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); +} + +static void nan_pairing_key_installed_cb(const uint8_t *peer_nmi, + uint8_t role, + uint8_t pairing_verification, + uint8_t ndp_csid, + const uint8_t *nd_pmk, + size_t nd_pmk_len, + uint32_t nik_lifetime_sec) +{ + (void)pairing_verification; + + if (!peer_nmi) { + return; + } + + const struct nan_pasn_key_material *keys = nan_pasn_get_saved_keys(); + uint8_t peer_remote_svc_id = 0; + bool npk_nik_caching = true; + uint8_t own_svc_id = 0; + + NAN_DATA_LOCK(); + struct peer_svc_info *peer = nan_find_peer_svc(0, 0, (uint8_t *)peer_nmi); + if (peer) { + struct own_svc_info *own = nan_find_own_svc(peer->own_svc_id); + + peer_remote_svc_id = peer->svc_id; + own_svc_id = peer->own_svc_id; + if (own) { + npk_nik_caching = own->pairing.npk_nik_caching; + } + if (keys && keys->pmk_len && keys->pmk_len <= ESP_WIFI_NAN_NPK_LEN && + peer->has_nik) { + uint8_t npk_tmp[ESP_WIFI_NAN_NPK_LEN] = {0}; + + memcpy(npk_tmp, keys->pmk, keys->pmk_len); + nan_app_update_peer_creds(peer->peer_nik, npk_tmp, + own ? own->svc_hash : NULL); + } + } + NAN_DATA_UNLOCK(); + + uint32_t lifetime_sec = nik_lifetime_sec; + + /* Cache ND-PMK for future paired NDPs (Wi-Fi Aware v4.0 §7.6.4.2). */ + if (ndp_csid && nd_pmk && nd_pmk_len == ESP_WIFI_NAN_NDP_PMK_LEN) { + (void)nan_app_register_paired_peer(peer_nmi, role, ndp_csid, + nd_pmk, nd_pmk_len, + lifetime_sec); + } else { + ESP_LOGW(TAG, "Pairing complete for " MACSTR + ": ND-PMK unavailable (csid=%u nd_pmk_len=%u); " + "paired-peer cache not updated", + MAC2STR(peer_nmi), ndp_csid, (unsigned)nd_pmk_len); + } + + /* + * Re-pair verification has no follow-up exchange (the initiator + * goes straight to NDP). Post PAIRING_CONFIRM here so the application + * is notified that PASN re-pairing is complete. + */ + if (nan_pairing_take_verify_session(peer_nmi) && + (role == NAN_ROLE_PAIRING_RESPONDER || role == NAN_ROLE_PAIRING_INITIATOR)) { + /* Re-verification: no follow-up ping-pong; notify app directly. */ + esp_nan_complete_pairing(own_svc_id, peer_remote_svc_id); + nan_pairing_post_confirm(peer_nmi); + return; + } + + if (own_svc_id && !npk_nik_caching) { + wifi_event_nan_pairing_complete_t evt = {0}; + + evt.status = WIFI_NAN_PAIRING_STATUS_ACCEPTED; + evt.reason_code = 0; + MACADDR_COPY(evt.peer_nmi, peer_nmi); + esp_nan_complete_pairing(own_svc_id, peer_remote_svc_id); + nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); + return; + } + + if (role == NAN_ROLE_PAIRING_INITIATOR) { + + struct nan_pairing_fup_ctx *ctx = os_zalloc(sizeof(*ctx)); + if (!ctx) { + ESP_LOGW(TAG, "Pairing key installed: failed to alloc fup ctx for " MACSTR, + MAC2STR(peer_nmi)); + return; + } + + ctx->svc_id = own_svc_id; + ctx->peer_svc_id = peer_remote_svc_id; + + /* Without a peer service entry the follow-up would carry zero service + * IDs; drop it rather than send an invalid frame. */ + if (!ctx->svc_id || !ctx->peer_svc_id) { + ESP_LOGW(TAG, "Pairing key installed: peer service not found for " MACSTR + ", skipping initiator follow-up", MAC2STR(peer_nmi)); + os_free(ctx); + return; + } + + MACADDR_COPY(ctx->peer_mac, peer_nmi); + ctx->shared_key_attr_len = 0; + + bool armed_pending = false; + NAN_DATA_LOCK(); + struct own_svc_info *own = nan_find_own_svc(ctx->svc_id); + if (own) { + nan_pairing_arm_pending(own, peer_nmi); + armed_pending = true; + } + NAN_DATA_UNLOCK(); + + if (eloop_register_timeout(0, 0, nan_app_send_pairing_followup_eloop, NULL, ctx) != 0) { + ESP_LOGW(TAG, "Pairing key installed: failed to schedule initiator follow-up"); + if (armed_pending) { + NAN_DATA_LOCK(); + own = nan_find_own_svc(ctx->svc_id); + if (own) { + nan_pairing_cancel_svc_pending(own); + } + NAN_DATA_UNLOCK(); + } + os_free(ctx); + } + return; + } + + if (role == NAN_ROLE_PAIRING_RESPONDER) { + NAN_DATA_LOCK(); + struct own_svc_info *own = nan_find_own_svc(own_svc_id); + if (own) { + nan_pairing_arm_pending(own, peer_nmi); + } + NAN_DATA_UNLOCK(); + } +} + +esp_err_t esp_wifi_nan_pairing_start(wifi_nan_pairing_config_t *cfg) +{ + if (!cfg) { + ESP_LOGE(TAG, "Pairing config NULL"); + return ESP_ERR_INVALID_ARG; + } + +#if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) + if (!cfg->pairing_verification && + cfg->cred.pincode != UINT32_MAX && + cfg->cred.pincode > NAN_PAIRING_PINCODE_MAX) { + ESP_LOGE(TAG, "Invalid pincode %u (valid range %u..%u or UINT32_MAX for default)", + cfg->cred.pincode, NAN_PAIRING_PINCODE_MIN, NAN_PAIRING_PINCODE_MAX); + return ESP_ERR_INVALID_ARG; + } +#endif + + int ret; + + switch (cfg->self_role) { + case NAN_PAIRING_ROLE_RESPONDER: + /* Verify-session is marked on Auth1 RX after NIRA OK in + * handle_auth_pasn(); initiator-side verify marks in pairing_start(). */ + if (!cfg->pairing_verification) { + esp_nan_pairing_clear_verify_session(cfg->peer_nmi); + } + ret = esp_nan_supp_pasn_responder_init(cfg->peer_nmi, cfg->cred.pincode, + 0, + nan_pairing_key_installed_cb); + if (ret != 0) { + ESP_LOGE(TAG, "NAN PASN responder init failed for "MACSTR, MAC2STR(cfg->peer_nmi)); + return ESP_FAIL; + } + break; + case NAN_PAIRING_ROLE_INITIATOR: + if (cfg->pairing_verification) { + esp_nan_pairing_mark_verify_session( + nan_pairing_resolve_own_inst(cfg->peer_svc_id, cfg->peer_nmi), + cfg->peer_nmi); + ret = esp_nan_supp_pasn_initiator_verify(cfg->peer_nmi, + nan_pairing_key_installed_cb); + } else { + esp_nan_pairing_clear_verify_session(cfg->peer_nmi); + ret = esp_nan_supp_pasn_initiator_auth(cfg->peer_nmi, cfg->cred.pincode, + 0, + nan_pairing_key_installed_cb); + } + if (ret != 0) { + ESP_LOGE(TAG, "NAN PASN initiator %s failed for "MACSTR, + cfg->pairing_verification ? "verify" : "auth", + MAC2STR(cfg->peer_nmi)); + return ESP_FAIL; + } + break; + default: + ESP_LOGE(TAG, "Invalid pairing role %d", cfg->self_role); + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +} + 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, @@ -870,9 +1120,7 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, if (shared_key_attr[0] != NAN_ATTR_ID_SHARED_KEY_DESC) { return; } - const uint16_t *attr_body_len_field = (const uint16_t *)&shared_key_attr[1]; - - attr_body_len = *attr_body_len_field; + attr_body_len = WPA_GET_LE16(&shared_key_attr[1]); if (attr_body_len > shared_key_attr_buf_len - 3) { ESP_LOGW(TAG, "Pairing follow-up: truncated Shared Key Descriptor (body=%zu, avail=%zu)", attr_body_len, shared_key_attr_buf_len); @@ -898,20 +1146,31 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, NAN_DATA_LOCK(); struct peer_svc_info *p_peer_svc = nan_find_peer_svc_exact(svc_id, peer_svc_id, peer_mac); + if (!p_peer_svc) { + /* NDP may have created a peer entry with peer_svc_id=0 before follow-up. */ + p_peer_svc = nan_find_peer_svc(svc_id, 0, (uint8_t *)peer_mac); + } if (p_peer_svc) { already_had_nik = p_peer_svc->has_nik; memcpy(p_peer_svc->peer_nik, nik, NAN_APP_PEER_NIK_LEN); p_peer_svc->peer_nik_cipher_ver = cipher_ver; p_peer_svc->peer_nik_lifetime_sec = lifetime_sec; p_peer_svc->has_nik = true; + s_nan_ctx.nik_lifetime = lifetime_sec; 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. */ + /* Refresh peer_creds (and NVS when enabled) with peer NIK and PASN NPK. */ const struct nan_paired_peer *paired = nan_app_find_paired_peer(peer_mac); - if (paired) { + const struct nan_pasn_key_material *keys = nan_pasn_get_saved_keys(); + const uint8_t *npk_src = NULL; + + if (keys && keys->pmk_len && keys->pmk_len <= ESP_WIFI_NAN_NPK_LEN) { + memcpy(persist_npk, keys->pmk, keys->pmk_len); + npk_src = persist_npk; + } else if (paired) { memcpy(persist_npk, paired->nd_pmk, ESP_WIFI_NAN_NPK_LEN); + npk_src = persist_npk; } own = nan_find_own_svc(p_peer_svc->own_svc_id); @@ -920,8 +1179,10 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, pairing_completed = true; } - nan_app_update_peer_creds(nik, paired ? paired->nd_pmk : NULL, own ? own->svc_hash : NULL); - persist_creds = s_nan_ctx.use_nvs_for_caching; + nan_app_update_peer_creds(nik, npk_src, own ? own->svc_hash : NULL); + if (s_nan_ctx.nik_lifetime == 0 && s_nan_ctx.use_nvs_for_caching) { + persist_creds = true; + } } NAN_DATA_UNLOCK(); @@ -929,18 +1190,18 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, if (persist_creds) { esp_wifi_nan_save_creds_for_peer(nik, persist_npk, own ? own->svc_hash : NULL); } + /* Invoke blocking calls outside NAN_DATA_LOCK to avoid deadlock. */ if (pairing_completed) { wifi_event_nan_pairing_complete_t evt = {0}; if (own) { nan_pairing_cancel_svc_pending(own); + esp_nan_complete_pairing(own->svc_id, peer_svc_id); } evt.status = WIFI_NAN_PAIRING_STATUS_ACCEPTED; evt.reason_code = 0; MACADDR_COPY(evt.peer_nmi, peer_mac); - esp_nan_complete_pairing(p_peer_svc ? p_peer_svc->own_svc_id : 0, - p_peer_svc ? p_peer_svc->svc_id : peer_svc_id); nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); } @@ -973,13 +1234,22 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, } } -bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_attr_len) +/* Core NIRA verification. When @a own_inst_id is non-NULL it also resolves the + * matched identity to a live own service instance id (0 if the verifying NIK + * does not map to an active local service), used to anchor a verify-session + * flag. The bool result reflects identity match only. */ +static bool nan_verify_nira_internal(uint8_t *peer_mac, uint8_t *nira_attr, + uint16_t nira_attr_len, uint8_t *own_inst_id) { uint8_t expected_tag[NAN_NIRA_TAG_LEN]; const uint8_t *nonce; const uint8_t *received_tag; bool match = false; + if (own_inst_id) { + *own_inst_id = 0; + } + if (!peer_mac || !nira_attr) { return false; } @@ -1000,13 +1270,24 @@ bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_at 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; + if (own_inst_id) { + for (int idx = 0; idx < ESP_WIFI_NAN_MAX_SVC_SUPPORTED; idx++) { + struct own_svc_info *own = &s_nan_ctx.own_svc[idx]; + + if (own->svc_id && + memcmp(own->svc_hash, + s_nan_ctx.peer_creds[i].service_hash, 6) == 0) { + *own_inst_id = own->svc_id; + break; + } + } + } break; } } @@ -1015,9 +1296,27 @@ bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_at if (match) { ESP_LOGD(TAG, "NIRA verify: OK for "MACSTR, MAC2STR(peer_mac)); } else { - ESP_LOGD(TAG, "NIRA verify: no matching NIK for "MACSTR, MAC2STR(peer_mac)); + ESP_LOGW(TAG, "NIRA verify: no matching NIK for "MACSTR, MAC2STR(peer_mac)); } return match; } +bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_attr_len) +{ + return nan_verify_nira_internal(peer_mac, nira_attr, nira_attr_len, NULL); +} + +bool esp_nan_verify_nira_get_own_svc(uint8_t *peer_mac, uint8_t *nira_attr, + uint16_t nira_attr_len, uint8_t *own_inst_id) +{ + return nan_verify_nira_internal(peer_mac, nira_attr, nira_attr_len, own_inst_id); +} + +#if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) +esp_nan_pairing_key_installed_cb_t esp_nan_pairing_get_key_installed_cb(void) +{ + return nan_pairing_key_installed_cb; +} +#endif + #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c index 9ec1cdb3ec8..f25a2e70cd9 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c @@ -14,6 +14,7 @@ #include #include +#include #include "esp_wifi.h" #include "esp_private/wifi.h" #include "esp_log.h" @@ -315,30 +316,70 @@ static int nan_ndp_ptk_derive(const uint8_t *pmk, const uint8_t *i_addr, const u return 0; } +static bool nan_mac_is_zero(const uint8_t mac[6]) +{ + static const uint8_t zero[6] = {0}; + + return memcmp(mac, zero, 6) == 0; +} + +/* + * Resolve IAddr/RAddr for NDP PTK derivation. When NDPE has not yet assigned + * an NDI, fall back to the peer NMI so both sides derive the same PTK. + * Returns 0 on success, -1 if our NAN MAC cannot be retrieved. + */ +static int nan_ndp_ptk_addrs(const struct ndl_info *ndl, bool responder, + uint8_t i_addr[6], uint8_t r_addr[6]) +{ + uint8_t our_mac[6]; + + if (esp_wifi_get_mac(WIFI_IF_NAN, our_mac) != ESP_OK) { + ESP_LOGE(TAG, "nan_ndp_ptk_addrs: get our MAC failed"); + return -1; + } + if (responder) { + if (nan_mac_is_zero(ndl->peer_ndi)) { + memcpy(i_addr, ndl->peer_nmi, 6); + } else { + memcpy(i_addr, ndl->peer_ndi, 6); + } + memcpy(r_addr, our_mac, 6); + } else { + memcpy(i_addr, our_mac, 6); + if (nan_mac_is_zero(ndl->peer_ndi)) { + memcpy(r_addr, ndl->peer_nmi, 6); + } else { + memcpy(r_addr, ndl->peer_ndi, 6); + } + } + return 0; +} + /* * Lazy initiator PTK derivation. No-op if ndl->ptk_set is already 1. * Caller MUST hold NAN_DATA_LOCK. On success, ndl->nd_kck/kek/tk and * the corresponding *_len fields are populated and ptk_set=1. * - * Spec §7.1.3.5: PTK PRF takes Data Interface addresses. Local NDI is - * obtained via esp_wifi_get_mac(WIFI_IF_NAN); peer NDI lives in - * ndl->peer_ndi, populated by ndp_response_indication on M2 RX. + * Spec §7.1.3.5: PTK PRF takes Data Interface addresses resolved via + * nan_ndp_ptk_addrs (peer NDI when assigned, else peer NMI). * * Returns 0 on success, -1 on failure. */ static int ndl_ensure_ptk(struct ndl_info *ndl) { + uint8_t i_addr[6]; + uint8_t r_addr[6]; + if (ndl->ptk_set) { return 0; } - uint8_t our_mac[6]; - if (esp_wifi_get_mac(WIFI_IF_NAN, our_mac) != ESP_OK) { - ESP_LOGE(TAG, "ndl_ensure_ptk: get our MAC failed"); + if (nan_ndp_ptk_addrs(ndl, false, i_addr, r_addr) != 0) { + ESP_LOGE(TAG, "ndl_ensure_ptk: PTK address resolution failed"); return -1; } if (nan_ndp_ptk_derive(ndl->security_ctx.nd_pmk, - our_mac, /* IAddr = Initiator NDI (us) */ - ndl->peer_ndi, /* RAddr = Responder NDI (peer) */ + i_addr, + r_addr, ndl->anonce, ndl->snonce, ndl->nd_kck, ndl->nd_kek, ndl->nd_tk) != 0) { ESP_LOGE(TAG, "ndl_ensure_ptk: PTK derivation failed"); @@ -445,29 +486,32 @@ static bool nan_ndp_resp_resolve_pmk(struct ndl_info *ndl, const uint8_t *peer_n return have_peer_pmkid; } + bool pmk_resolved = false; + + if (have_peer_pmkid) { + struct own_svc_info *p_svc = nan_find_own_svc(ndl->publisher_id); + int matched_idx = p_svc ? nan_match_pmkid(p_svc, ndl->security_ctx.nd_pmkid, + ndl->peer_nmi, ndl->peer_ndi) : -1; + if (matched_idx >= 0) { + ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED; + ndl->security_ctx.csid_bitmap = p_svc->derived_security[matched_idx].csid_bitmap; + memcpy(ndl->security_ctx.nd_pmk, + p_svc->derived_security[matched_idx].nd_pmk, + ESP_WIFI_NAN_NDP_PMK_LEN); + ESP_LOGD(TAG, "NDP Resp Key Desc: resolved PMK from cred slot %d", matched_idx); + pmk_resolved = true; + } + } + #if defined(CONFIG_ESP_WIFI_NAN_PAIRING) - if (nan_security_fill_from_paired_cache(ndl, peer_nmi)) { - return have_peer_pmkid; + if (!pmk_resolved && need_pmk && + nan_security_fill_from_paired_cache(ndl, peer_nmi)) { + ESP_LOGD(TAG, "NDP Resp Key Desc: resolved PMK from paired-peer cache"); + pmk_resolved = true; } #endif - if (!have_peer_pmkid) { - return false; - } - - struct own_svc_info *p_svc = nan_find_own_svc(ndl->publisher_id); - int matched_idx = p_svc ? nan_match_pmkid(p_svc, ndl->security_ctx.nd_pmkid, - ndl->peer_nmi, ndl->peer_ndi) : -1; - if (matched_idx < 0) { - return false; - } - - ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED; - ndl->security_ctx.csid_bitmap = p_svc->derived_security[matched_idx].csid_bitmap; - memcpy(ndl->security_ctx.nd_pmk, - p_svc->derived_security[matched_idx].nd_pmk, - ESP_WIFI_NAN_NDP_PMK_LEN); - return true; + return pmk_resolved; } uint8_t esp_nan_get_ndp_resp_num_pmkids(uint8_t ndp_id, const uint8_t *peer_nmi) @@ -1253,15 +1297,16 @@ int esp_nan_get_ndp_resp_shared_key_desc(uint8_t *buf, size_t buf_len, uint8_t n /* M2 echoes M1's replay counter unchanged (per RSNA 4-way handshake). */ memcpy(ndl->tx_replay_counter, ndl->rx_replay_counter, NAN_REPLAY_COUNTER_LEN); - uint8_t our_mac[6]; - if (esp_wifi_get_mac(WIFI_IF_NAN, our_mac) != ESP_OK) { + uint8_t i_addr[6]; + uint8_t r_addr[6]; + if (nan_ndp_ptk_addrs(ndl, true, i_addr, r_addr) != 0) { NAN_DATA_UNLOCK(); - ESP_LOGE(TAG, "NDP Resp Key Desc: get our MAC failed"); + ESP_LOGE(TAG, "NDP Resp Key Desc: PTK address resolution failed"); return 0; } if (nan_ndp_ptk_derive(ndl->security_ctx.nd_pmk, - ndl->peer_ndi, /* IAddr = Initiator NDI (Wi-Fi Aware v4.0 §7.1.3.5) */ - our_mac, /* RAddr = Responder NDI */ + i_addr, /* IAddr = Initiator NDI (Wi-Fi Aware v4.0 §7.1.3.5) */ + r_addr, /* RAddr = Responder NDI */ ndl->anonce, ndl->snonce, ndl->nd_kck, ndl->nd_kek, ndl->nd_tk) != 0) { NAN_DATA_UNLOCK(); @@ -1376,6 +1421,7 @@ int esp_nan_update_ndp_security_install_mic(uint8_t *m4_body, size_t body_len, u void esp_nan_parse_ndp_csia(void *frm, size_t buf_len, wifi_nan_security_params_t *param) { + if (!frm || !param || buf_len < 3) { return; } @@ -1416,6 +1462,7 @@ void esp_nan_parse_ndp_csia(void *frm, size_t buf_len, wifi_nan_security_params_ void esp_nan_parse_ndp_scia(void *frm, size_t buf_len, wifi_nan_security_params_t *param) { + if (!frm || !param || buf_len < 3) { return; } @@ -1710,6 +1757,7 @@ void nan_security_apply_pending(struct ndl_info *ndl, const uint8_t *peer_nmi, const uint8_t *peer_ndi) { + if (ndl && s_pending_scia.pub_id == pub_id && (s_pending_scia.has_csid || s_pending_scia.has_pmkid)) { @@ -1721,6 +1769,8 @@ void nan_security_apply_pending(struct ndl_info *ndl, if (s_pending_scia.has_pmkid) { memcpy(ndl->security_ctx.nd_pmkid, s_pending_scia.pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN); int matched_idx = nan_match_pmkid(p_own_svc, s_pending_scia.pmkid, peer_nmi, peer_ndi); + bool pmk_resolved = false; + if (matched_idx >= 0) { ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED; ndl->security_ctx.csid_bitmap = p_own_svc->derived_security[matched_idx].csid_bitmap; @@ -1728,13 +1778,15 @@ void nan_security_apply_pending(struct ndl_info *ndl, p_own_svc->derived_security[matched_idx].nd_pmk, ESP_WIFI_NAN_NDP_PMK_LEN); ESP_LOGD(TAG, "NDP Indication: PMKID validated via cred slot %d", matched_idx); + pmk_resolved = true; } #if defined(CONFIG_ESP_WIFI_NAN_PAIRING) else if (nan_security_fill_from_paired_cache(ndl, peer_nmi)) { ESP_LOGD(TAG, "NDP Indication: PMK sourced from paired-peer cache"); + pmk_resolved = true; } #endif - else { + if (!pmk_resolved) { ESP_LOGW(TAG, "NDP Indication: PMKID validation failed"); } } diff --git a/components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h b/components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h index 865b93ac18b..0d8a6c07e51 100644 --- a/components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h +++ b/components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h @@ -61,7 +61,10 @@ enum nan_role { * Pairing Initiator NMI || Pairing Responder NMI)). * * @param peer_nmi Peer NMI (6 bytes). - * @param role enum nan_role value for the local device. + * @param role @c NAN_ROLE_PAIRING_INITIATOR or @c NAN_ROLE_PAIRING_RESPONDER. + * @param pairing_verification 1 when PASN ran as re-pair verification (NIRA verified on + * Auth1 for responder, or @c cfg->pairing_verification on + * initiator); 0 for bootstrap auth pairing. * @param ndp_csid NCS-SK CSID for paired-peer NDP (WIFI_NAN_CSID_NCS_SK_128 * or _SK_256), 0 if no usable cipher mapping was available. * @param nd_pmk ND-PMK bytes (32) or NULL if KDK was absent. @@ -73,6 +76,7 @@ enum nan_role { */ typedef void (*esp_nan_pairing_key_installed_cb_t)(const uint8_t *peer_nmi, uint8_t role, + uint8_t pairing_verification, uint8_t ndp_csid, const uint8_t *nd_pmk, size_t nd_pmk_len, @@ -133,6 +137,23 @@ int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode, uint32_t nik_lifetime_sec, esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb); +/** + * @brief Schedule NAN PASN initiator verification (re-pair with cached NIK/PMK). + * + * Same role as @ref esp_nan_supp_pasn_initiator_auth but uses PASN verify instead of auth. + * + * @param peer_nmi Peer NMI (6 bytes). + * @param pairing_key_installed_cb Callback invoked after pairwise key installation with peer NMI. + * @return 0 on success, -1 on failure. + */ +int esp_nan_supp_pasn_initiator_verify(const uint8_t *peer_nmi, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb); + +/** + * @brief Default pairing key-installed callback (NAN app layer). + */ +esp_nan_pairing_key_installed_cb_t esp_nan_pairing_get_key_installed_cb(void); + /** * Schedule @ref handle_auth_pasn from NAN app callback table. */ @@ -141,6 +162,26 @@ void handle_auth_pasn(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t sta const struct nan_pasn_key_material *nan_pasn_get_saved_keys(void); void nan_pasn_clear_saved_keys(void); +/** + * Clear stale pairwise keys, saved PASN material, and active NDPs before + * responder-side PASN verification (proactive pairing_start or passive Auth1). + */ +void nan_pasn_responder_verify_prepare(const uint8_t *peer_nmi); + +/** Look up cached NPK for pairing verification (§7.6.5). Returns 0 on success. + * Lookup matches peer_cred.service_hash against active own-service hashes, + * or uses a single cached slot when only one NPK is present. */ +int nan_global_peer_npk_lookup(uint8_t *npk, size_t *npk_len, int *akmp); + +/** + * Mark/clear that the current PASN session for @a peer_nmi is pairing + * verification (re-pair) and must not trigger NIK follow-up exchange. The + * marker is anchored to own service @a own_inst_id (as resolved from the + * verifying NIK), so it survives even when no peer_svc_info exists yet. + */ +void esp_nan_pairing_mark_verify_session(uint8_t own_inst_id, const uint8_t *peer_nmi); +void esp_nan_pairing_clear_verify_session(const uint8_t *peer_nmi); + /** * Decrypt a NAN Shared Key Descriptor attribute received in a follow-up frame. * diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h index 044cdcf1c83..26e797aeea7 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h @@ -9,6 +9,7 @@ #if CONFIG_ESP_WIFI_NAN_PAIRING +#include #include #include #include "esp_private/esp_supp_nan.h" @@ -17,6 +18,8 @@ struct wpabuf; struct pasn_data; struct rsn_pmksa_cache; +#define NAN_PASN_PMKID_LEN 16 + typedef esp_nan_pairing_key_installed_cb_t nan_pasn_pairing_key_installed_cb_t; struct nan_config { @@ -43,20 +46,20 @@ struct nan_pasn_data { size_t pasn_ptk_len; struct pasn_data *pasn; nan_pasn_pairing_key_installed_cb_t pairing_key_installed_cb; + uint8_t pairing_verification; uint32_t nik_lifetime_sec; + bool pasn_auth2_done; /**< Initiator: Auth2 already processed (dedup) */ + uint8_t peer_npkid[NAN_PASN_PMKID_LEN]; /**< Nonce||Tag from peer NIRA; for PMKID cross-check */ + bool peer_npkid_valid; }; int nan_initiate_pasn_verify(struct nan_pasn_data *pd, const uint8_t *peer_addr, - int freq, int role, const uint8_t *bssid, + int role, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len); -int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const uint8_t *addr, int freq); +int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const uint8_t *addr); struct nan_pasn_data *nan_pasn_data_init(void); void nan_pasn_data_deinit(struct nan_pasn_data *pd); -int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, int freq); -int nan_pasn_auth(struct nan_pasn_data **pd_out, const uint8_t *peer_addr, int freq); -int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs, - const uint8_t *peer_addr, int freq, int role, - const uint8_t *bssid, - const uint8_t *ssid, size_t ssid_len); +int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr); +int nan_pasn_auth(struct nan_pasn_data **pd_out, const uint8_t *peer_addr); #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c index b5e2eee2840..69bf45263f5 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c @@ -16,6 +16,7 @@ #include "utils/includes.h" #include "utils/common.h" #include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" #include "common/nan.h" #include "esp_wifi_driver.h" #include "crypto/crypto.h" @@ -49,12 +50,74 @@ static struct nan_pasn_key_material g_nan_pasn_saved_keys; -/* Key index for esp_wifi_set_nan_key_internal (NAN PASN pairwise TK). */ -int temp = 1; +#define NAN_NIRA_NONCE_LEN 8 +#define NAN_NIRA_TAG_LEN 8 +#define NAN_NIRA_ATTR_LEN 20 +#define NAN_PASN_CSIA_ATTR_MAX_LEN (3 + 1 + 2 * 8) +#define NAN_PASN_PAIRING_BOOTSTRAP_METHODS WIFI_NAN_BOOTSTRAP_PIN_CODE_DISPLAY +#define NAN_PASN_PAIRING_CSIA_PUB_ID 5 + +static int nan_pasn_npkid_from_nira_attr(const u8 *nira, u16 nira_len, u8 *npkid) +{ + if (!nira || !npkid || + nira_len < 4 + NAN_NIRA_NONCE_LEN + NAN_NIRA_TAG_LEN) { + return -1; + } + + /* NPKID = Nonce || Tag (wire order matches NIRA body after Cipher Version; + * PMKID is carried little-endian in the RSNE). */ + os_memcpy(npkid, nira + 4, NAN_NIRA_NONCE_LEN); + os_memcpy(npkid + NAN_NIRA_NONCE_LEN, + nira + 4 + NAN_NIRA_NONCE_LEN, NAN_NIRA_TAG_LEN); + return 0; +} + +static int nan_pasn_build_local_npkid(u8 *npkid) +{ + uint8_t nira_frm[NAN_NIRA_ATTR_LEN]; + + if (!npkid || + esp_nan_construct_nira(nira_frm) < + (int)(4 + NAN_NIRA_NONCE_LEN + NAN_NIRA_TAG_LEN)) { + return -1; + } + + return nan_pasn_npkid_from_nira_attr(nira_frm, NAN_NIRA_ATTR_LEN, npkid); +} + +static int nan_validate_custom_pmkid(void *ctx, const u8 *addr, const u8 *pmkid) +{ + struct nan_pasn_data *nan = ctx; + + if (!pmkid) { + return -1; + } + + /* + * Pairing verification: NIK is already checked via NIRA and NPK via + * PMKSA lookup, but §7.6.5 also binds RSNE PMKID to the same-frame NIRA + * (NPKID = Nonce||Tag). Cross-check when we cached NPKID from that NIRA. + */ + if (!nan || !nan->peer_npkid_valid) { + return 0; + } + + if (os_memcmp(pmkid, nan->peer_npkid, PMKID_LEN) != 0) { + wpa_printf(MSG_INFO, + "NAN PASN verify: PMKID/NPKID mismatch for " MACSTR, + MAC2STR(addr)); + return -1; + } + + return 0; +} #define NAN_PASN_AES_WRAP_OVERHEAD 8 #define NAN_PASN_AES_WRAP_MIN_CIPHERTEXT (NAN_PASN_AES_WRAP_OVERHEAD + 8) +static int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb); + /** * Map PASN pairwise cipher to the NCS-SK CSID used for paired-peer NDPs * (Wi-Fi Aware v4.0 §7.6.4.2: paired NDP runs NCS-SK style M1-M4, key length @@ -239,7 +302,6 @@ static int nan_pasn_install_nan_pairwise_tk(struct nan_pasn_data *nan, struct pa struct wpa_ptk *ptk; uint8_t key_rsc[8] = {0}; int kret; - (void)nan; if (!pasn) { return -1; @@ -258,7 +320,7 @@ static int nan_pasn_install_nan_pairwise_tk(struct nan_pasn_data *nan, struct pa wpa_hexdump_key(MSG_DEBUG, "NAN PASN: NM-TK", ptk->tk, ptk->tk_len); kret = esp_wifi_set_nan_key_internal( - NAN_PASN_WIFI_ALG_CCMP, pasn->peer_addr, temp, 1, key_rsc, sizeof(key_rsc), + NAN_PASN_WIFI_ALG_CCMP, pasn->peer_addr, 1, 1, key_rsc, sizeof(key_rsc), ptk->tk, ptk->tk_len, NAN_KEY_NM_TK); if (kret != 0) { @@ -428,6 +490,21 @@ void nan_pasn_clear_saved_keys(void) forced_memzero(&g_nan_pasn_saved_keys, sizeof(g_nan_pasn_saved_keys)); } +static void nan_pasn_clear_peer_tks_for_verify_start(const u8 *peer_nmi) +{ + if (!peer_nmi) { + return; + } + esp_nan_app_clear_peer_tks(peer_nmi, 0); + nan_pasn_clear_saved_keys(); +} + +void nan_pasn_responder_verify_prepare(const u8 *peer_nmi) +{ + esp_nan_app_end_peer_datapaths(peer_nmi); + nan_pasn_clear_peer_tks_for_verify_start(peer_nmi); +} + /* NAN KDE OUI Type values from Wi-Fi Aware spec v4.0, Table 126. */ #define NAN_PASN_KDE_OUI_TYPE_NIK 36 #define NAN_PASN_KDE_OUI_TYPE_LIFETIME 37 @@ -749,28 +826,6 @@ out: return ret; } -static int nan_chan_to_freq_mhz(uint8_t chan) -{ - if (chan >= 1 && chan <= 13) { - return 2407 + (int)chan * 5; - } - if (chan == 14) { - return 2484; - } - return 0; -} - -static int nan_pasn_get_current_freq_mhz(void) -{ - uint8_t primary = 0; - wifi_second_chan_t second = WIFI_SECOND_CHAN_NONE; - - if (esp_wifi_get_channel(&primary, &second) != ESP_OK || primary == 0) { - return 0; - } - return nan_chan_to_freq_mhz(primary); -} - static void nan_pasn_auth_timeout_cancel(struct nan_pasn_data *nan); static void nan_pasn_auth_timeout_cb(void *eloop_ctx, void *user_data) @@ -846,48 +901,97 @@ static int nan_set_dev_sae_pin(struct nan_pasn_data *nan, const char *digits) } /* - * Build Wi-Fi Alliance NAN vendor IE (EID 221) with DCEA / BPBA / CSIA for PASN - * and pass to @a pasn via pasn_set_extra_ies() so wpa_pasn_add_extra_ies() appends + * Build Wi-Fi Alliance NAN vendor IE (EID 221) for PASN. + * Bootstrap auth: DCEA + NPBA + CSIA. + * Pairing verification: CSIA + NIRA only (no DCEA/NPBA). + * Passed to @a pasn via pasn_set_extra_ies() so wpa_pasn_add_extra_ies() appends * it after PASN Parameters on Auth 1/3 (and after prepare_data_element on Auth 2). */ static int nan_prepare_pasn_extra_ie(struct nan_pasn_data *nan, struct pasn_data *pasn, - const struct wpabuf *frame, bool add_dira) + const struct wpabuf *frame, bool include_nira) { - static const u8 nan_pasn_attr_payload[] = { - NAN_ATTR_DCEA, 0x02, 0x00, 0x00, 0x03, - NAN_ATTR_BPBA, 0x05, 0x00, 0x90, 0x02, 0x00, 0x02, 0x00, - NAN_ATTR_CSIA, 0x03, 0x00, 0x00, 0x07, 0x05, - }; - u8 fixed[2 + 3 + 1 + sizeof(nan_pasn_attr_payload)]; + u8 csia_attr[NAN_PASN_CSIA_ATTR_MAX_LEN]; + u8 nira_attr[NAN_NIRA_ATTR_LEN]; + uint8_t *pairing_attrs = NULL; u8 *buf = NULL; + u8 *pos; size_t fr_len = 0; + size_t csia_len; + size_t nira_len = 0; + size_t attr_payload_len; + size_t vendor_ie_len; size_t total_len; + uint32_t npba_len = 0; + uint32_t dcea_len = 0; + uint32_t pairing_attrs_len = 0; int ret; (void)nan; - (void)add_dira; - fixed[0] = WLAN_EID_VENDOR_SPECIFIC; - fixed[1] = 3 + 1 + sizeof(nan_pasn_attr_payload); - WPA_PUT_BE24(&fixed[2], OUI_WFA); - fixed[5] = NAN_OUI_TYPE; - os_memcpy(&fixed[6], nan_pasn_attr_payload, sizeof(nan_pasn_attr_payload)); + ret = esp_nan_construct_csia(csia_attr, NAN_PASN_PAIRING_CSIA_PUB_ID, + WIFI_NAN_CSID_BIT_NCS_PK_PASN_128, 0); + if (ret <= 0 || ret > (int)sizeof(csia_attr)) { + wpa_printf(MSG_INFO, "NAN PASN: CSIA build failed"); + return -1; + } + csia_len = (size_t) ret; + attr_payload_len = csia_len; + + if (include_nira) { + ret = esp_nan_construct_nira(nira_attr); + if (ret < (int)NAN_NIRA_ATTR_LEN) { + wpa_printf(MSG_INFO, "NAN PASN: NIRA build failed"); + return -1; + } + nira_len = (size_t) ret; + attr_payload_len += nira_len; + } else { + pairing_attrs = esp_wifi_nan_get_pairing_attrs(NAN_PASN_PAIRING_BOOTSTRAP_METHODS, + true, true, &npba_len, + &dcea_len, &pairing_attrs_len); + if (!pairing_attrs || !pairing_attrs_len) { + wpa_printf(MSG_INFO, "NAN PASN: pairing attrs build failed"); + return -1; + } + attr_payload_len += pairing_attrs_len; + } if (frame) { fr_len = wpabuf_len(frame); } - if (!fr_len) { - return pasn_set_extra_ies(pasn, fixed, sizeof(fixed)); + vendor_ie_len = 3 + 1 + attr_payload_len; + if (vendor_ie_len > UINT8_MAX) { + wpa_printf(MSG_INFO, "NAN PASN: extra IE too long"); + return -1; } - total_len = sizeof(fixed) + fr_len; + total_len = 2 + vendor_ie_len + fr_len; buf = os_malloc(total_len); if (!buf) { return -1; } - os_memcpy(buf, fixed, sizeof(fixed)); - os_memcpy(buf + sizeof(fixed), wpabuf_head_u8(frame), fr_len); + + pos = buf; + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = (u8) vendor_ie_len; + WPA_PUT_BE24(pos, OUI_WFA); + pos += 3; + *pos++ = NAN_OUI_TYPE; + if (pairing_attrs_len) { + os_memcpy(pos, pairing_attrs, pairing_attrs_len); + pos += pairing_attrs_len; + } + os_memcpy(pos, csia_attr, csia_len); + pos += csia_len; + if (nira_len) { + os_memcpy(pos, nira_attr, nira_len); + pos += nira_len; + } + if (fr_len) { + os_memcpy(pos, wpabuf_head_u8(frame), fr_len); + } + ret = pasn_set_extra_ies(pasn, buf, total_len); os_free(buf); return ret; @@ -962,7 +1066,7 @@ static void nan_pairing_apply_sae_pin(struct pasn_data *pasn, u8 pasn_type, pasn->password = nan->dev_sae_pin; } -void nan_pasn_initialize(struct nan_pasn_data *nan, const u8 *addr, int freq, bool verify, bool derive_kek) +void nan_pasn_initialize(struct nan_pasn_data *nan, const u8 *addr) { struct pasn_data *pasn; struct wpabuf *rsnxe; @@ -971,6 +1075,8 @@ void nan_pasn_initialize(struct nan_pasn_data *nan, const u8 *addr, int freq, bo return; } + nan->peer_npkid_valid = false; + if (nan->pasn) { wpa_pasn_reset(nan->pasn); } else { @@ -1021,15 +1127,13 @@ void nan_pasn_initialize(struct nan_pasn_data *nan, const u8 *addr, int freq, bo * ND-PMK is filled by pasn_nd_pmk_derive_from_kdk_store (hostap * nan_crypto_derive_nd_pmk_from_kdk). Matches hostap nan_pairing.c. */ - (void)derive_kek; pasn->derive_kek = false; pasn->kek_len = 0; + /* Wi-Fi Aware pairing: PASN Auth frames always use SAE as the base AKM. */ + pasn->akmp = WPA_KEY_MGMT_SAE; if (nan->dev_sae_pin_len > 0) { - pasn->akmp = WPA_KEY_MGMT_SAE; nan_pairing_apply_sae_pin(pasn, nan->cfg->pasn_type, nan); - } else if (!verify) { - pasn->akmp = WPA_KEY_MGMT_PASN; } pasn->rsn_pairwise = pasn->cipher; @@ -1059,16 +1163,19 @@ void nan_pasn_initialize(struct nan_pasn_data *nan, const u8 *addr, int freq, bo pasn->parse_data_element = nan->cfg->parse_data_element; pasn->validate_custom_pmkid = nan->cfg->pasn_validate_pmkid; - pasn->freq = freq; - } int nan_initiate_pasn_verify(struct nan_pasn_data *pd, const u8 *peer_addr, - int freq, int role, + int role, const u8 *bssid, const u8 *ssid, size_t ssid_len) { struct nan_pasn_data *nan; struct pasn_data *pasn; + uint8_t npk[NAN_PASN_KEY_PMK_MAX]; + uint8_t own_addr[ETH_ALEN]; + u8 npkid[PMKID_LEN]; + size_t npk_len = 0; + int akmp = WPA_KEY_MGMT_SAE; int ret = 0; (void)role; @@ -1087,29 +1194,55 @@ int nan_initiate_pasn_verify(struct nan_pasn_data *pd, const u8 *peer_addr, } nan->dev_role = NAN_ROLE_PAIRING_INITIATOR; - nan_pasn_initialize(nan, peer_addr, freq, true, true); + nan->pasn_auth2_done = false; + nan->pairing_verification = 1; + nan_pasn_clear_peer_tks_for_verify_start(peer_addr); + nan_pasn_initialize(nan, peer_addr); pasn = nan->pasn; - if (nan_prepare_pasn_extra_ie(nan, pasn, NULL, false) != 0) { - wpa_printf(MSG_INFO, "NAN PASN: extra IE failed"); + if (!pasn) { return -1; } + nan->cfg->pasn_validate_pmkid = nan_validate_custom_pmkid; + pasn->validate_custom_pmkid = nan_validate_custom_pmkid; + + /* §7.6.5: seed PMKSA with cached NPK and local NPKID (Nonce||Tag). */ + if (nan_global_peer_npk_lookup(npk, &npk_len, &akmp) != 0 || + esp_wifi_get_mac(WIFI_IF_NAN, own_addr) != ESP_OK || + nan_pasn_build_local_npkid(npkid) != 0) { + ret = -1; + goto out; + } + + if (pasn_initiator_pmksa_cache_add(nan->initiator_pmksa, own_addr, + (u8 *) peer_addr, npk, npk_len, npkid) != 0) { + ret = -1; + goto out; + } + + pasn_set_akmp(pasn, akmp); + pasn->wpa_key_mgmt = akmp; + pasn_set_custom_pmkid(pasn, npkid); + + if (nan_prepare_pasn_extra_ie(nan, pasn, NULL, true) != 0) { + ret = -1; + goto out; + } + if (wpa_pasn_verify(pasn, pasn->own_addr, pasn->peer_addr, pasn->bssid, - pasn->akmp, pasn->cipher, pasn->group, pasn->freq, + pasn->akmp, pasn->cipher, pasn->group, 0, NULL, 0, NULL, 0, NULL)) { wpa_printf(MSG_INFO, "PASN verify failed"); ret = -1; } - if (pasn->extra_ies) { - os_free((u8 *) pasn->extra_ies); - pasn->extra_ies = NULL; - pasn->extra_ies_len = 0; - } + +out: + forced_memzero(npk, sizeof(npk)); return ret; } -int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const u8 *addr, int freq) +int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const u8 *addr) { struct nan_pasn_data *nan; struct pasn_data *pasn; @@ -1126,10 +1259,16 @@ int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const u8 *addr, int freq) } nan->dev_role = NAN_ROLE_PAIRING_INITIATOR; + nan->pasn_auth2_done = false; + nan->pairing_verification = 0; - nan_pasn_initialize(nan, addr, freq, false, true); + nan_pasn_initialize(nan, addr); pasn = nan->pasn; + if (!pasn) { + return -1; + } + pasn_initiator_pmksa_cache_remove(pasn->pmksa, (u8 *)addr); if (nan_prepare_pasn_extra_ie(nan, pasn, NULL, false) != 0) { @@ -1138,7 +1277,7 @@ int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const u8 *addr, int freq) } if (wpas_pasn_start(pasn, pasn->own_addr, pasn->peer_addr, pasn->bssid, - pasn->akmp, pasn->cipher, pasn->group, pasn->freq, + pasn->akmp, pasn->cipher, pasn->group, 0, NULL, 0, NULL, 0, NULL)) { wpa_printf(MSG_INFO, "Failed to start PASN"); ret = -1; @@ -1167,15 +1306,13 @@ int * int_array_dup(const int *a) } static int nan_handle_pasn_auth(struct nan_pasn_data *nan, - const struct ieee80211_auth *mgmt, size_t len, - int freq) + const struct ieee80211_auth *mgmt, size_t len) { struct pasn_data *pasn; u8 pasn_type; int pasn_groups[4] = { 0 }; u16 auth_alg, auth_transaction, status_code; - (void)freq; if (!nan || !nan->pasn) { return -1; } @@ -1246,6 +1383,7 @@ static int nan_handle_pasn_auth(struct nan_pasn_data *nan, nan_pasn_store_ptk(nan, &pasn->ptk); #endif /* CONFIG_TESTING_OPTIONS */ nan_pasn_copy_keys_from_pasn(nan, pasn); + /* Install NM-TK only after Auth3 is successfully validated. */ if (nan_pasn_install_nan_pairwise_tk(nan, pasn) == 0 && nan->pairing_key_installed_cb) { const uint8_t *nd_pmk = pasn_nd_pmk_global.valid ? @@ -1253,6 +1391,7 @@ static int nan_handle_pasn_auth(struct nan_pasn_data *nan, size_t nd_pmk_len = pasn_nd_pmk_global.valid ? PMK_LEN : 0; nan->pairing_key_installed_cb(pasn->peer_addr, (uint8_t)nan->dev_role, + nan->pairing_verification, nan_pasn_pasn_cipher_to_ndp_csid(pasn->cipher), nd_pmk, nd_pmk_len, nan->nik_lifetime_sec); @@ -1263,8 +1402,108 @@ static int nan_handle_pasn_auth(struct nan_pasn_data *nan, return 0; } +/** + * Find a NIRA attribute inside a WFA NAN vendor-specific IE (OUI + type 0x13). + * Returns a pointer to the attribute (ID + length + body) or NULL. + */ +static const u8 *nan_pasn_find_nira_attr(const u8 *ies, size_t ies_len, u16 *attr_len) +{ + const u8 *ie = ies; + const u8 *end = ies + ies_len; + + while (ie + 2 <= end) { + u8 id = ie[0]; + u8 elen = ie[1]; + + if (ie + 2 + elen > end) { + break; + } + + if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4 && + WPA_GET_BE24(ie + 2) == OUI_WFA && ie[5] == NAN_OUI_TYPE) { + const u8 *pos = ie + 6; + const u8 *attrs_end = ie + 2 + elen; + + while (attrs_end - pos >= NAN_ATTR_HDR_LEN) { + u8 attr_id = pos[0]; + u16 attr_body_len = WPA_GET_LE16(pos + 1); + u16 total_attr_len; + + if (attr_body_len > (size_t)(attrs_end - pos - NAN_ATTR_HDR_LEN)) { + break; + } + + total_attr_len = NAN_ATTR_HDR_LEN + attr_body_len; + if (attr_id == NAN_ATTR_NIRA) { + if (attr_len) { + *attr_len = total_attr_len; + } + return pos; + } + pos += total_attr_len; + } + } + ie += 2 + elen; + } + + return NULL; +} + +/* Find NIRA in @a mgmt once, verify identity, and stash NPKID for PMKID + * cross-check. When @a own_inst_id is non-NULL the matched own service id is + * also returned (0 if none). */ +static bool nan_pasn_auth_process_nira(struct nan_pasn_data *nan, + const u8 *peer_addr, + const struct ieee80211_auth *mgmt, + size_t len, u16 auth_trans, + uint8_t *own_inst_id) +{ + const u8 *var; + size_t var_len; + const u8 *nira; + u16 nira_len = 0; + + if (own_inst_id) { + *own_inst_id = 0; + } + + if (!nan || !peer_addr || !mgmt) { + return false; + } + + nan->peer_npkid_valid = false; + + if (len < offsetof(struct ieee80211_auth, auth.variable)) { + return false; + } + + var = mgmt->auth.variable; + var_len = len - offsetof(struct ieee80211_auth, auth.variable); + nira = nan_pasn_find_nira_attr(var, var_len, &nira_len); + if (!nira) { + wpa_printf(MSG_INFO, "NAN PASN verify: NIRA missing in Auth%u from " + MACSTR, auth_trans, MAC2STR(peer_addr)); + return false; + } + + if (!esp_nan_verify_nira_get_own_svc((u8 *) peer_addr, (u8 *) nira, nira_len, + own_inst_id)) { + wpa_printf(MSG_INFO, "NAN PASN verify: NIRA verification failed in " + "Auth%u for " MACSTR, auth_trans, MAC2STR(peer_addr)); + return false; + } + + if (nan_pasn_npkid_from_nira_attr(nira, nira_len, nan->peer_npkid) == 0) { + nan->peer_npkid_valid = true; + } + + wpa_printf(MSG_DEBUG, "NAN PASN verify: NIRA OK in Auth%u for " MACSTR, + auth_trans, MAC2STR(peer_addr)); + return true; +} + int nan_pasn_auth_rx(struct nan_pasn_data *nan, const struct ieee80211_auth *mgmt, - size_t len, int freq) + size_t len) { int ret = 0; u16 auth_transaction; @@ -1293,6 +1532,23 @@ int nan_pasn_auth_rx(struct nan_pasn_data *nan, const struct ieee80211_auth *mgm if (nan->dev_role == NAN_ROLE_PAIRING_INITIATOR && auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH2) { + /* + * Duplicate Auth2: responder may retransmit if it didn't get Auth3 + * ACK. wpa_pasn_auth_rx() would reject with trans_seq mismatch and + * we would also lose NM-TK that was already installed; silently + * accept the retransmit instead. + */ + if (nan->pasn_auth2_done) { + return 0; + } + + if (pasn->custom_pmkid_valid && + !nan_pasn_auth_process_nira(nan, mgmt->sa, mgmt, len, 2, NULL)) { + wpa_printf(MSG_INFO, "PASN: Auth2 NIRA verify failed"); + nan->dev_role = NAN_ROLE_IDLE; + return -1; + } + ret = wpa_pasn_auth_rx(pasn, (const u8 *) mgmt, len, &pasn_data); if (ret < 0) { wpa_printf(MSG_INFO, "PASN: wpa_pasn_auth_rx() failed"); @@ -1304,13 +1560,15 @@ int nan_pasn_auth_rx(struct nan_pasn_data *nan, const struct ieee80211_auth *mgm * successfully built/transmitted by wpa_pasn_auth_rx(). */ nan_pasn_copy_keys_from_pasn(nan, pasn); - if (nan_pasn_install_nan_pairwise_tk(nan, pasn) == 0 && - nan->pairing_key_installed_cb) { + nan->pasn_auth2_done = true; + int tk_ret = nan_pasn_install_nan_pairwise_tk(nan, pasn); + if (tk_ret == 0 && nan->pairing_key_installed_cb) { const uint8_t *nd_pmk = pasn_nd_pmk_global.valid ? pasn_nd_pmk_global.nd_pmk : NULL; size_t nd_pmk_len = pasn_nd_pmk_global.valid ? PMK_LEN : 0; nan->pairing_key_installed_cb(pasn->peer_addr, (uint8_t)nan->dev_role, + nan->pairing_verification, nan_pasn_pasn_cipher_to_ndp_csid(pasn->cipher), nd_pmk, nd_pmk_len, nan->nik_lifetime_sec); @@ -1321,7 +1579,7 @@ int nan_pasn_auth_rx(struct nan_pasn_data *nan, const struct ieee80211_auth *mgm #endif /* CONFIG_TESTING_OPTIONS */ forced_memzero(pasn_get_ptk(pasn), sizeof(pasn->ptk)); } else { - ret = nan_handle_pasn_auth(nan, mgmt, len, freq); + ret = nan_handle_pasn_auth(nan, mgmt, len); } return ret; } @@ -1330,7 +1588,8 @@ void handle_auth_pasn(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t sta { const struct ieee80211_auth *mgmt; struct nan_pasn_data *nan; - int rx_freq; + bool auth1_verify = false; + u8 auth1_pmkid[PMKID_LEN]; (void)trans_seq; (void)status; @@ -1340,15 +1599,114 @@ void handle_auth_pasn(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t sta } mgmt = (const struct ieee80211_auth *)buf; nan = esp_nan_app_get_pasn_data(); - rx_freq = nan_pasn_get_current_freq_mhz(); - if (rx_freq <= 0) { - rx_freq = 2412; + if (!nan) { + /* + * Only lazy-init on Auth1: a stray Auth2/Auth3 (e.g. retransmit after + * we already completed PASN and tore down the session) must be + * dropped, otherwise we would init a fresh context and try to process + * a mid-handshake frame against it, which fails noisily. + */ + if (le_to_host16(mgmt->auth.auth_transaction) != + WLAN_AUTH_TR_SEQ_PASN_AUTH1) { + return; + } + if (pasn_responder_init(mgmt->sa, UINT32_MAX, + esp_nan_pairing_get_key_installed_cb()) != 0) { + return; + } + nan = esp_nan_app_get_pasn_data(); } if (!nan) { wpa_printf(MSG_DEBUG, "NAN PASN: receive_pasn: no context"); return; } - nan_pasn_auth_rx(nan, mgmt, len, rx_freq); + + if (le_to_host16(mgmt->auth.auth_transaction) == 1) { + struct ieee802_11_elems elems; + struct wpa_ie_data rsn_data; + struct wpa_pasn_params_data pasn_params; + + if (ieee802_11_parse_elems(mgmt->auth.variable, + len - offsetof(struct ieee80211_auth, auth.variable), + &elems, 0) != ParseFailed && + elems.rsn_ie && elems.pasn_params && + wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2, + &rsn_data) == 0 && + rsn_data.num_pmkid && + wpa_pasn_parse_parameter_ie(elems.pasn_params - 3, + elems.pasn_params_len + 3, + false, &pasn_params) == 0 && + pasn_params.wrapped_data_format == WPA_PASN_WRAPPED_DATA_NO) { + auth1_verify = true; + os_memcpy(auth1_pmkid, rsn_data.pmkid, PMKID_LEN); + } + } + + if (auth1_verify) { + uint8_t npk[NAN_PASN_KEY_PMK_MAX]; + uint8_t own_addr[ETH_ALEN]; + size_t npk_len = 0; + int akmp = WPA_KEY_MGMT_SAE; + struct pasn_data *pasn = nan->pasn; + bool auth1_ok = false; + uint8_t verify_own_inst_id = 0; + + if (!nan_pasn_auth_process_nira(nan, mgmt->sa, mgmt, len, 1, + &verify_own_inst_id)) { + goto auth1_verify_done; + } + nan_pasn_responder_verify_prepare(mgmt->sa); + + if (!pasn) { + wpa_printf(MSG_INFO, "NAN PASN verify: no PASN context for " + MACSTR, MAC2STR(mgmt->sa)); + goto auth1_verify_done; + } + if (nan_global_peer_npk_lookup(npk, &npk_len, &akmp) != 0) { + wpa_printf(MSG_INFO, "NAN PASN verify: no cached NPK for " + MACSTR, MAC2STR(mgmt->sa)); + goto auth1_verify_done; + } + if (esp_wifi_get_mac(WIFI_IF_NAN, own_addr) != ESP_OK) { + wpa_printf(MSG_INFO, "NAN PASN verify: failed to read NAN NMI"); + goto auth1_verify_done; + } + if (pasn_responder_pmksa_cache_add(nan->responder_pmksa, own_addr, + (u8 *) mgmt->sa, npk, npk_len, + auth1_pmkid) != 0) { + wpa_printf(MSG_INFO, "NAN PASN verify: PMKSA cache add failed for " + MACSTR, MAC2STR(mgmt->sa)); + goto auth1_verify_done; + } + + { + u8 npkid[PMKID_LEN]; + + nan->cfg->pasn_validate_pmkid = nan_validate_custom_pmkid; + pasn->validate_custom_pmkid = nan_validate_custom_pmkid; + pasn_set_akmp(pasn, akmp); + pasn->wpa_key_mgmt = akmp; + if (nan_pasn_build_local_npkid(npkid) == 0) { + pasn_set_custom_pmkid(pasn, npkid); + } + if (nan_prepare_pasn_extra_ie(nan, pasn, NULL, true) != 0) { + wpa_printf(MSG_INFO, "NAN PASN verify: extra IE (NIRA) build " + "failed for " MACSTR, MAC2STR(mgmt->sa)); + goto auth1_verify_done; + } + auth1_ok = true; + nan->pairing_verification = 1; + } + +auth1_verify_done: + forced_memzero(npk, sizeof(npk)); + if (!auth1_ok) { + return; + } + esp_nan_pairing_mark_verify_session(verify_own_inst_id, mgmt->sa); + } + + nan_pasn_auth_rx(nan, mgmt, len); } void nan_pasn_pmksa_set_pmk(struct nan_pasn_data *nan, const u8 *src, const u8 *dst, @@ -1539,16 +1897,14 @@ void nan_pasn_data_deinit(struct nan_pasn_data *pd) os_free(pd); } -int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, int freq) +int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr) { if (!pd || !peer_addr) { return -1; } - return nan_initiate_pasn_auth(pd, peer_addr, freq); + return nan_initiate_pasn_auth(pd, peer_addr); } -#define NAN_PASN_VERIFY_ELOOP_SSID_MAX 32 - struct nan_pasn_eloop_ctx { uint8_t peer_addr[ETH_ALEN]; uint32_t pincode; @@ -1556,10 +1912,37 @@ struct nan_pasn_eloop_ctx { esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb; }; +static struct nan_pasn_data *nan_pasn_eloop_prepare(struct nan_pasn_eloop_ctx *ctx, + const char *pin_to_apply) +{ + struct nan_pasn_data *old; + struct nan_pasn_data *pd; + + old = esp_nan_app_get_pasn_data(); + if (old) { + nan_pasn_data_deinit(old); + } + + pd = nan_pasn_data_init(); + if (!pd) { + return NULL; + } + + esp_nan_app_set_pasn_data(pd); + pd->pairing_key_installed_cb = ctx->pairing_key_installed_cb; + pd->nik_lifetime_sec = ctx->nik_lifetime_sec; + + if (pin_to_apply && nan_set_dev_sae_pin(pd, pin_to_apply) != 0) { + nan_pasn_data_deinit(pd); + return NULL; + } + + return pd; +} + static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data) { struct nan_pasn_eloop_ctx *ctx = user_data; - struct nan_pasn_data *old; struct nan_pasn_data *pd; char pin_digits[16]; int n; @@ -1570,46 +1953,23 @@ static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data) return; } - old = esp_nan_app_get_pasn_data(); - if (old) { - nan_pasn_data_deinit(old); - } - - pd = nan_pasn_data_init(); - if (!pd) { - os_free(ctx); - return; - } - - esp_nan_app_set_pasn_data(pd); - pd->pairing_key_installed_cb = ctx->pairing_key_installed_cb; - pd->nik_lifetime_sec = ctx->nik_lifetime_sec; - if (ctx->pincode != UINT32_MAX) { n = os_snprintf(pin_digits, sizeof(pin_digits), "%06u", (unsigned)ctx->pincode); if (os_snprintf_error(sizeof(pin_digits), n)) { - nan_pasn_data_deinit(pd); os_free(ctx); return; } pin_to_apply = pin_digits; } - if (pin_to_apply && nan_set_dev_sae_pin(pd, pin_to_apply) != 0) { - nan_pasn_data_deinit(pd); + pd = nan_pasn_eloop_prepare(ctx, pin_to_apply); + if (!pd) { os_free(ctx); return; } - { - int freq = nan_pasn_get_current_freq_mhz(); - - if (freq <= 0) { - freq = 2412; - } - nan_pasn_auth_initiate(pd, ctx->peer_addr, freq); - } + nan_pasn_auth_initiate(pd, ctx->peer_addr); os_free(ctx); } @@ -1619,8 +1979,12 @@ int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode, { struct nan_pasn_eloop_ctx *ctx; + if (!peer_nmi) { + return -1; + } + ctx = os_zalloc(sizeof(*ctx)); - if (!ctx || !peer_nmi) { + if (!ctx) { return -1; } @@ -1637,49 +2001,32 @@ int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode, return 0; } -struct nan_pasn_verify_eloop_ctx { - uint8_t peer_addr[ETH_ALEN]; - int freq; - int role; - uint8_t bssid[ETH_ALEN]; - uint8_t ssid[NAN_PASN_VERIFY_ELOOP_SSID_MAX]; - size_t ssid_len; -}; - -static void nan_pasn_verify_eloop_cb(void *eloop_ctx, void *user_data) +static void nan_pasn_verify_init_eloop_cb(void *eloop_ctx, void *user_data) { - struct nan_pasn_verify_eloop_ctx *ctx = user_data; + struct nan_pasn_eloop_ctx *ctx = user_data; struct nan_pasn_data *pd; - const uint8_t *ssid_arg; (void)eloop_ctx; if (!ctx) { return; } - pd = esp_nan_app_get_pasn_data(); + pd = nan_pasn_eloop_prepare(ctx, NULL); if (!pd) { os_free(ctx); return; } - ssid_arg = ctx->ssid_len ? ctx->ssid : NULL; - nan_initiate_pasn_verify(pd, ctx->peer_addr, ctx->freq, ctx->role, - ctx->bssid, ssid_arg, ctx->ssid_len); + nan_initiate_pasn_verify(pd, ctx->peer_addr, 0, NULL, NULL, 0); os_free(ctx); } -int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs, - const uint8_t *peer_addr, int freq, int role, - const uint8_t *bssid, - const uint8_t *ssid, size_t ssid_len) +int esp_nan_supp_pasn_initiator_verify(const uint8_t *peer_nmi, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb) { - struct nan_pasn_verify_eloop_ctx *ctx; + struct nan_pasn_eloop_ctx *ctx; - if (!peer_addr) { - return -1; - } - if (ssid_len > NAN_PASN_VERIFY_ELOOP_SSID_MAX) { + if (!peer_nmi) { return -1; } @@ -1688,20 +2035,10 @@ int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs, return -1; } - os_memcpy(ctx->peer_addr, peer_addr, ETH_ALEN); - ctx->freq = freq; - ctx->role = role; - if (bssid) { - os_memcpy(ctx->bssid, bssid, ETH_ALEN); - } else { - os_memcpy(ctx->bssid, peer_addr, ETH_ALEN); - } - if (ssid && ssid_len) { - os_memcpy(ctx->ssid, ssid, ssid_len); - ctx->ssid_len = ssid_len; - } + os_memcpy(ctx->peer_addr, peer_nmi, ETH_ALEN); + ctx->pairing_key_installed_cb = pairing_key_installed_cb; - if (eloop_register_timeout(secs, usecs, nan_pasn_verify_eloop_cb, NULL, ctx) != 0) { + if (eloop_register_timeout(0, 0, nan_pasn_verify_init_eloop_cb, NULL, ctx) != 0) { os_free(ctx); return -1; } @@ -1717,9 +2054,11 @@ int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs, * * @param peer_addr Peer NAN address, or NULL to use a broadcast placeholder until Auth1. * @param pincode 6-digit value 0..999999, or @c UINT32_MAX to keep default PIN from @ref nan_pasn_data_init. + * @param pairing_key_installed_cb Callback stored on the responder PASN context. * Returns 0 on success, -1 on failure. */ -int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode) +int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb) { struct nan_pasn_data *pd; struct nan_pasn_data *old; @@ -1728,11 +2067,20 @@ int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode) const u8 *peer = peer_addr ? peer_addr : bcast; const char *pin_to_apply = NULL; int n; - int freq; os_memset(pin_digits, 0, sizeof(pin_digits)); old = esp_nan_app_get_pasn_data(); + /* Lazy Auth1 init and scheduled bootstrap init can both call this; do not + * tear down an in-progress responder session for the same peer. */ + if (old && old->dev_role == NAN_ROLE_PAIRING_RESPONDER && old->pasn && + !is_broadcast_ether_addr(peer) && + os_memcmp(old->pasn->peer_addr, peer, ETH_ALEN) == 0) { + if (pairing_key_installed_cb) { + old->pairing_key_installed_cb = pairing_key_installed_cb; + } + return 0; + } if (old) { nan_pasn_data_deinit(old); } @@ -1743,6 +2091,7 @@ int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode) } esp_nan_app_set_pasn_data(pd); + pd->pairing_key_installed_cb = pairing_key_installed_cb; if (pincode != UINT32_MAX) { n = os_snprintf(pin_digits, sizeof(pin_digits), "%06u", @@ -1757,13 +2106,8 @@ int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode) goto fail; } - freq = nan_pasn_get_current_freq_mhz(); - if (freq <= 0) { - freq = 2412; - } - pd->dev_role = NAN_ROLE_PAIRING_RESPONDER; - nan_pasn_initialize(pd, peer, freq, false, true); + nan_pasn_initialize(pd, peer); if (!pd->pasn || nan_prepare_pasn_extra_ie(pd, pd->pasn, NULL, false) != 0) { goto fail; @@ -1786,16 +2130,15 @@ struct pasn_responder_eloop_ctx { static void pasn_responder_init_eloop_cb(void *eloop_ctx, void *user_data) { struct pasn_responder_eloop_ctx *ctx = user_data; - struct nan_pasn_data *pd; (void)eloop_ctx; if (!ctx) { return; } - if (pasn_responder_init(ctx->peer_addr, ctx->pincode) == 0) { - pd = esp_nan_app_get_pasn_data(); + if (pasn_responder_init(ctx->peer_addr, ctx->pincode, + ctx->pairing_key_installed_cb) == 0) { + struct nan_pasn_data *pd = esp_nan_app_get_pasn_data(); if (pd) { - pd->pairing_key_installed_cb = ctx->pairing_key_installed_cb; pd->nik_lifetime_sec = ctx->nik_lifetime_sec; } } 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 909183f78c5..a0fcca370bd 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h @@ -342,6 +342,9 @@ 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_complete_pairing(uint8_t svc_id, uint8_t peer_svc_id); +uint8_t *esp_wifi_nan_get_pairing_attrs(uint16_t bootstrap_methods, bool pairing_enabled, + bool nik_cache_enabled, uint32_t *npba_len, + uint32_t *dcea_len, uint32_t *total_len); 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]); diff --git a/components/wpa_supplicant/src/ap/ieee802_11.h b/components/wpa_supplicant/src/ap/ieee802_11.h index 6062aab86e8..1565c9a4d8d 100644 --- a/components/wpa_supplicant/src/ap/ieee802_11.h +++ b/components/wpa_supplicant/src/ap/ieee802_11.h @@ -10,11 +10,12 @@ #define IEEE802_11_H enum wpa_validate_result; -int auth_sae_queued_addr(struct hostapd_data *hapd, const u8 *addr); +#ifdef CONFIG_SAE int auth_sae_queue(struct hostapd_data *hapd, u8 *buf, size_t len, u8 *bssid, u16 status, u32 auth_transaction); int handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, u8 *buf, size_t len, u8 *bssid, u16 auth_transaction, u16 status); +#endif /* CONFIG_SAE */ u16 wpa_res_to_status_code(enum wpa_validate_result res); #ifdef CONFIG_OWE_SOFTAP uint16_t owe_process_assoc_req(struct hostapd_data *hapd, struct sta_info *sta, const u8 *owe_dh, diff --git a/components/wpa_supplicant/src/ap/pmksa_cache_auth.c b/components/wpa_supplicant/src/ap/pmksa_cache_auth.c index 3376174e80a..ffe619ea710 100644 --- a/components/wpa_supplicant/src/ap/pmksa_cache_auth.c +++ b/components/wpa_supplicant/src/ap/pmksa_cache_auth.c @@ -52,7 +52,8 @@ void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, unsigned int hash; pmksa->pmksa_count--; - pmksa->free_cb(entry, pmksa->ctx); + if (pmksa->free_cb) + pmksa->free_cb(entry, pmksa->ctx); /* unlink from hash list */ hash = PMKID_HASH(entry->pmkid); diff --git a/components/wpa_supplicant/src/common/nan.h b/components/wpa_supplicant/src/common/nan.h index 19ab7468711..4c239f02a05 100644 --- a/components/wpa_supplicant/src/common/nan.h +++ b/components/wpa_supplicant/src/common/nan.h @@ -48,7 +48,7 @@ enum nan_attr_id { NAN_ATTR_NDP_EXT = 0x29, NAN_ATTR_DCEA = 0x2A, /* Device Capability Extension attribute */ NAN_ATTR_NIRA = 0x2B, /* NAN Identity Resolution attribute */ - NAN_ATTR_BPBA = 0x2C, /* NAN Pairing Bootstrapping attribute */ + NAN_ATTR_NPBA = 0x2C, /* NAN Pairing Bootstrapping attribute */ NAN_ATTR_S3 = 0x2D, NAN_ATTR_TPEA = 0x2E, /* Transmit Power Envelope attribute */ NAN_ATTR_VENDOR_SPECIFIC = 0xDD, diff --git a/components/wpa_supplicant/src/pasn/pasn_initiator.c b/components/wpa_supplicant/src/pasn/pasn_initiator.c index 47faab5e619..17c3aa01939 100644 --- a/components/wpa_supplicant/src/pasn/pasn_initiator.c +++ b/components/wpa_supplicant/src/pasn/pasn_initiator.c @@ -624,6 +624,9 @@ static struct wpabuf * wpas_pasn_build_auth_1(struct pasn_data *pasn, #else /* CONFIG_IEEE80211R */ goto fail; #endif /* CONFIG_IEEE80211R */ + } else if (verify && pasn->custom_pmkid_valid) { + /* Wi-Fi Aware pairing verification: NPKID in RSNE, no wrapped data */ + pmkid = pasn->custom_pmkid; } else if (wrapped_data != WPA_PASN_WRAPPED_DATA_NO) { struct rsn_pmksa_cache_entry *pmksa; diff --git a/components/wpa_supplicant/src/rsn_supp/pmksa_cache.c b/components/wpa_supplicant/src/rsn_supp/pmksa_cache.c index 6e303415c25..710de74ef17 100644 --- a/components/wpa_supplicant/src/rsn_supp/pmksa_cache.c +++ b/components/wpa_supplicant/src/rsn_supp/pmksa_cache.c @@ -46,7 +46,8 @@ static void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, enum pmksa_free_reason reason) { pmksa->pmksa_count--; - pmksa->free_cb(entry, pmksa->ctx, reason); + if (pmksa->free_cb) + pmksa->free_cb(entry, pmksa->ctx, reason); _pmksa_cache_free_entry(entry); }