From 3e81bc86c76f41c7a82e330707e897a160d06187 Mon Sep 17 00:00:00 2001 From: Akshat Agrawal Date: Thu, 4 Jun 2026 21:01:08 +0530 Subject: [PATCH] fix(nan): fix NAN pairing NIK/NIRA exchange and verification Register esp_nan_verify_nira, cache NIRA for publish frames, send own_nik in pairing follow-up, and complete pairing only after peer NIK is stored. --- .../esp_wifi/include/esp_private/wifi.h | 12 + .../esp_wifi/include/esp_wifi_types_generic.h | 4 +- .../include/injected/esp_wifi_types_generic.h | 4 +- .../esp_wifi/wifi_apps/nan_app/src/nan_app.c | 9 + .../esp_wifi/wifi_apps/nan_app/src/nan_i.h | 4 +- .../wifi_apps/nan_app/src/nan_pairing.c | 260 ++++++++++++------ .../esp_supplicant/src/esp_wifi_driver.h | 1 + 7 files changed, 211 insertions(+), 83 deletions(-) diff --git a/components/esp_wifi/include/esp_private/wifi.h b/components/esp_wifi/include/esp_private/wifi.h index a8eb783f9a3..f69b4e43388 100644 --- a/components/esp_wifi/include/esp_private/wifi.h +++ b/components/esp_wifi/include/esp_private/wifi.h @@ -195,6 +195,7 @@ struct nan_sync_callbacks { void (* receive_pasn)(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t status); 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); }; /* Host helpers for NAN encrypted-datapath, registered via @@ -1197,6 +1198,17 @@ uint32_t esp_nan_get_nira_len(void); */ int esp_nan_construct_nira(uint8_t *frm); +/** + * @brief Verify a received NAN Identity Resolution Attribute (NIRA) + * + * @param[in] peer_mac NMI of the sender + * @param[in] nira_attr NIRA attribute buffer + * @param[in] nira_attr_len Attribute length in bytes + * + * @return true if the tag matches, false otherwise + */ +bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_attr_len); + /** * @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 8adb1ddd2f9..3e28f14e7b3 100644 --- a/components/esp_wifi/include/esp_wifi_types_generic.h +++ b/components/esp_wifi/include/esp_wifi_types_generic.h @@ -1580,8 +1580,8 @@ typedef struct { } wifi_event_nan_replied_t; /** - * @brief Argument structure for WIFI_EVENT_NAN_CLUSTER_JOIN event - */ + * @brief Argument structure for WIFI_EVENT_NAN_CLUSTER_JOIN event + */ typedef struct { uint8_t cluster_id[6]; /**< Cluster ID (BSSID) that was joined/started */ } wifi_event_nan_cluster_join_t; 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 397e577ebab..e4e98fcc114 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 @@ -1580,8 +1580,8 @@ typedef struct { } wifi_event_nan_replied_t; /** - * @brief Argument structure for WIFI_EVENT_NAN_CLUSTER_JOIN event - */ + * @brief Argument structure for WIFI_EVENT_NAN_CLUSTER_JOIN event + */ typedef struct { uint8_t cluster_id[6]; /**< Cluster ID (BSSID) that was joined/started */ } wifi_event_nan_cluster_join_t; 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 a8a137ad20e..fea7ddfff64 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 @@ -59,6 +59,14 @@ int esp_nan_construct_nira(uint8_t *frm) (void)frm; return 0; } + +bool esp_nan_verify_nira(uint8_t *peer_mac, uint8_t *nira_attr, uint16_t nira_attr_len) +{ + (void)peer_mac; + (void)nira_attr; + (void)nira_attr_len; + return false; +} #endif #if defined(CONFIG_ESP_WIFI_NAN_SYNC_ENABLE) && defined(CONFIG_ESP_WIFI_PASN_SUPPORT) @@ -1435,6 +1443,7 @@ void esp_nan_action_start(esp_netif_t *nan_netif) #ifdef CONFIG_ESP_WIFI_NAN_PAIRING .get_nira_len = esp_nan_get_nira_len, .construct_nira = esp_nan_construct_nira, + .verify_nira = esp_nan_verify_nira, .receive_pasn = handle_auth_pasn, #endif }; 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 80064086d8a..54eeb252000 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 @@ -287,9 +287,11 @@ typedef struct { struct own_svc_info own_svc[ESP_WIFI_NAN_MAX_SVC_SUPPORTED]; esp_netif_t *nan_netif; #ifdef CONFIG_ESP_WIFI_NAN_SECURITY - /* Own NAN Identity Key (NIK) cached for pairing/security flows. */ uint8_t own_nik[ESP_WIFI_NAN_NIK_LEN]; bool own_nik_valid; + uint8_t cached_nira_nonce[8]; + uint8_t cached_nira_tag[8]; + bool cached_nira_valid; #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 53c26be1959..c5d5ae120e1 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 @@ -21,6 +21,7 @@ #include "nan_i.h" #include "os.h" #include "utils/common.h" +#include "utils/eloop.h" #if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) #include "esp_private/esp_supp_nan.h" @@ -49,42 +50,7 @@ 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) -{ - wifi_event_nan_pairing_complete_t evt = {0}; - - if (!peer_nmi) { - return; - } - -#if defined(CONFIG_ESP_WIFI_NAN_SECURITY) - /* Cache ND-PMK for future paired NDPs (Wi-Fi Aware v4.0 §7.6.4.2). The - * NDP cipher (CSID) is determined by the PASN cipher and resolved on the - * supplicant side before this callback fires; if either is missing, the - * pairing event still fires but the security layer will fall back to its - * service-credential path for any subsequent NDP. */ - 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, - NAN_PAIRING_DEFAULT_NIK_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); - } -#else - (void)role; - (void)ndp_csid; - (void)nd_pmk; - (void)nd_pmk_len; -#endif - - evt.status = WIFI_NAN_PAIRING_STATUS_ACCEPTED; - evt.reason_code = 0; - MACADDR_COPY(evt.peer_nmi, peer_nmi); - nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); -} + size_t nd_pmk_len); #endif bool nan_pairing_validate_publish_bootstrapping(uint16_t bootstrapping_methods) @@ -334,53 +300,66 @@ uint32_t esp_nan_get_nira_len(void) int esp_nan_construct_nira(uint8_t *frm) { - uint8_t nonce[NAN_NIRA_NONCE_LEN]; - uint8_t tag[NAN_NIRA_TAG_LEN]; + const uint8_t *nonce; + const uint8_t *tag; + uint8_t fresh_nonce[NAN_NIRA_NONCE_LEN]; + uint8_t fresh_tag[NAN_NIRA_TAG_LEN]; if (!frm) { return 0; } - if (os_get_random(nonce, sizeof(nonce)) != 0) { - ESP_LOGE(TAG, "NIRA: failed to generate nonce"); - return 0; - } - #ifdef CONFIG_ESP_WIFI_NAN_SECURITY - uint8_t own_nmi[MACADDR_LEN]; - const unsigned char *addr[3]; - int len_arr[3]; - uint8_t digest[32]; + if (s_nan_ctx.cached_nira_valid) { + nonce = s_nan_ctx.cached_nira_nonce; + 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; - } + 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; + } + if (os_get_random(fresh_nonce, sizeof(fresh_nonce)) != 0) { + ESP_LOGE(TAG, "NIRA: failed to generate nonce"); + return 0; + } - /* Tag = Truncate-64(HMAC-SHA-256(NIK, "NIR" || NMI || Nonce)) */ - 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] = 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; + /* Tag = Truncate-64(HMAC-SHA-256(NIK, "NIR" || NMI || Nonce)) */ + 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); + s_nan_ctx.cached_nira_valid = true; + + nonce = fresh_nonce; + tag = fresh_tag; } - memcpy(tag, digest, NAN_NIRA_TAG_LEN); - memset(digest, 0, sizeof(digest)); #else /* NIRA requires an available NIK; skip when NAN security is not enabled. */ return 0; @@ -403,7 +382,6 @@ int esp_nan_construct_nira(uint8_t *frm) #if defined(CONFIG_ESP_WIFI_NAN_PAIRING) && defined(CONFIG_ESP_WIFI_PASN_SUPPORT) && defined(CONFIG_ESP_WIFI_NAN_SECURITY) #include "crypto/sha256.h" -#include "utils/eloop.h" #include "crypto/aes_wrap.h" #include "common/ieee802_11_defs.h" #include "common/wpa_common.h" @@ -680,9 +658,14 @@ static esp_err_t nan_app_send_pairing_followup(uint8_t svc_id, uint8_t peer_svc_ (void)shared_key_attr; (void)shared_key_attr_len; - if (!peer_mac || os_get_random(nik, sizeof(nik)) != 0) { + if (!peer_mac) { return ESP_ERR_INVALID_ARG; } + if (!s_nan_ctx.own_nik_valid) { + ESP_LOGW(TAG, "Pairing follow-up: own NIK is not available"); + return ESP_ERR_INVALID_STATE; + } + memcpy(nik, s_nan_ctx.own_nik, sizeof(nik)); saved = nan_pasn_get_saved_keys(); if (!saved || !saved->kek_len) { @@ -773,6 +756,7 @@ static void nan_app_send_pairing_followup_eloop(void *eloop_data, void *user_dat if (!ctx) { return; } + (void) nan_app_send_pairing_followup(ctx->svc_id, ctx->peer_svc_id, ctx->peer_mac, ctx->shared_key_attr, @@ -780,6 +764,62 @@ 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) +{ + if (!peer_nmi) { + return; + } + +#if defined(CONFIG_ESP_WIFI_NAN_SECURITY) + /* 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, + NAN_PAIRING_DEFAULT_NIK_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); + } +#else + (void)ndp_csid; + (void)nd_pmk; + (void)nd_pmk_len; +#endif + + 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(); + struct peer_svc_info *peer = nan_find_peer_svc(0, 0, (uint8_t *)peer_nmi); + if (peer) { + ctx->svc_id = peer->own_svc_id; + ctx->peer_svc_id = peer->svc_id; + } + NAN_DATA_UNLOCK(); + + MACADDR_COPY(ctx->peer_mac, peer_nmi); + ctx->shared_key_attr_len = 0; + + 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"); + os_free(ctx); + } + return; + } + (void)role; +} + 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, @@ -792,7 +832,6 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, uint32_t lifetime_sec = 0; struct nan_pairing_fup_ctx *ctx; size_t alloc_len; - if (!shared_key_attr || !peer_mac) { return; } @@ -802,7 +841,6 @@ 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; @@ -823,18 +861,39 @@ void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, return; } + bool already_had_nik = false; + 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) { + 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; ESP_LOGI(TAG, "Stored peer NIK from " MACSTR " (cipher_ver=%u, lifetime=%u s)", MAC2STR(peer_mac), cipher_ver, lifetime_sec); + + if (!already_had_nik) { + 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_mac); + esp_nan_disable_pairing(); + nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); + } } NAN_DATA_UNLOCK(); + /* Only reply with our own NIK the first time we receive the peer's. + * If has_nik was already true this is a redundant echo — don't reply + * or we create an infinite ping-pong of follow-ups. */ + if (already_had_nik) { + ESP_LOGD(TAG, "Pairing follow-up: NIK already known for " MACSTR ", skipping reply", + MAC2STR(peer_mac)); + return; + } + if (total_len > SIZE_MAX - sizeof(*ctx)) { return; } @@ -855,6 +914,51 @@ 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) +{ + uint8_t expected_tag[NAN_NIRA_TAG_LEN]; + const uint8_t *nonce; + const uint8_t *received_tag; + struct peer_svc_info *p_peer_svc; + bool match; + + if (!peer_mac || !nira_attr) { + return false; + } + + if (nira_attr_len < NAN_NIRA_ATTR_LEN) { + ESP_LOGW(TAG, "NIRA verify: attribute too short (%u < %u)", + (unsigned)nira_attr_len, (unsigned)NAN_NIRA_ATTR_LEN); + return false; + } + + nonce = nira_attr + 4; + received_tag = nira_attr + 4 + NAN_NIRA_NONCE_LEN; + + NAN_DATA_LOCK(); + p_peer_svc = nan_find_peer_svc(0, 0, peer_mac); + if (!p_peer_svc || !p_peer_svc->has_nik) { + NAN_DATA_UNLOCK(); + ESP_LOGD(TAG, "NIRA verify: no stored NIK for "MACSTR, MAC2STR(peer_mac)); + return false; + } + + if (nan_pairing_derive_nira_tag(p_peer_svc->peer_nik, peer_mac, nonce, expected_tag) != 0) { + NAN_DATA_UNLOCK(); + ESP_LOGE(TAG, "NIRA verify: tag derivation failed for "MACSTR, MAC2STR(peer_mac)); + return false; + } + NAN_DATA_UNLOCK(); + + match = (os_memcmp_const(expected_tag, received_tag, NAN_NIRA_TAG_LEN) == 0); + if (match) { + ESP_LOGD(TAG, "NIRA verify: OK for "MACSTR, MAC2STR(peer_mac)); + } else { + ESP_LOGW(TAG, "NIRA verify: tag mismatch for "MACSTR, MAC2STR(peer_mac)); + } + return match; +} + #endif /* CONFIG_ESP_WIFI_NAN_PAIRING && CONFIG_ESP_WIFI_PASN_SUPPORT && CONFIG_ESP_WIFI_NAN_SECURITY */ #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ 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 dcf3fed52f7..35e5e93a3ba 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h @@ -334,5 +334,6 @@ void esp_wifi_ap_set_group_mgmt_cipher_internal(wifi_cipher_type_t cipher); uint8_t esp_wifi_op_class_supported_internal(uint8_t op_class, uint8_t min_chan, uint8_t max_chan, uint8_t inc, uint8_t bw, channel_bitmap_t *non_pref_channels); bool esp_wifi_is_wpa3_compatible_mode_enabled(uint8_t if_index); uint8_t esp_wifi_ap_get_owe_config_internal(void); +esp_err_t esp_nan_disable_pairing(void); #endif /* _ESP_WIFI_DRIVER_H_ */