feat(nan): Add support for NAN Pairing Verification

- Add nira attr and verification for pasn auth frames
- Refine key clearing and pairing complete logic for pasn verify
- Add NIRA own-service resolution, cached NIK checks, and dynamic
  pairing IE construction for bootstrap vs verify paths.
- Replace NAN bootstrap events by private callbacks
This commit is contained in:
Sajia
2026-07-04 00:05:20 +08:00
committed by BOT
parent 0d8cb4445d
commit bf5907d066
18 changed files with 1641 additions and 533 deletions
@@ -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.
*
@@ -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
*/
@@ -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
*/
@@ -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
@@ -5,6 +5,7 @@
*/
#include <ctype.h>
#include <stdio.h>
#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;
@@ -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;
@@ -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 */
@@ -14,6 +14,7 @@
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#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");
}
}