feat(wifi): NAN multi-credential discovery security

Replace scalar passphrase/PMK in wifi_nan_discovery_security_params_t
with wifi_nan_credential_t[N] array (cap 4) per Wi-Fi Aware v4.0
§9.5.21.4. Drop public PMKID array and service-level csid_bitmap.

Rename wifi_nan_datapath_security_params_t -> wifi_nan_security_params_t;
add wifi_nan_peer_sdf_security_t for multi-PMKID peer-RX. The
derive_security_params callback takes (svc_name, sec_cfg, out_derived[])
and runs once per credential on the WiFi task. Split nan_record_own_svc
into claim/finalize so the WiFi-task callback finds the pending slot
by name and mirrors derived material — no main-task PBKDF2.
This commit is contained in:
Sarvesh Bodakhe
2026-05-14 02:18:10 +05:30
parent 2da41618f5
commit cfecf60986
8 changed files with 528 additions and 258 deletions

View File

@@ -48,19 +48,43 @@ typedef enum {
} wifi_nan_security_type_t;
/**
* @brief NAN Datapath security parameters (Spec 6.1.1 - Data Path Request/Response)
* @brief NAN single-PMKID security parameters
*
* @note Shared between WiFi libraries and NAN app layer.
* @note Holds one derived ND-PMK + ND-PMKID per spec §7.1.3.5. Used both for
* the blob's per-service derived cache (own-side Publish SDF SCIA TX)
* and for the host's per-NDL handshake state (M1-M4). Single PMKID is
* sufficient because every spec key derivation step yields exactly one
* PMKID for a given (PMK, peer NMIs, Service ID) tuple.
* Shared between WiFi libraries and NAN app layer.
*/
typedef struct {
wifi_nan_security_type_t type; /**< Security Type (Open/Encrypted) */
uint16_t csid_bitmap; /**< Bitmap of Cipher Suite IDs (WIFI_NAN_CSID_BIT_*) */
uint8_t nd_pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< ND-PMK (Required for Datapath) */
uint16_t csid_bitmap; /**< Selected Cipher Suite ID bit (WIFI_NAN_CSID_BIT_*) */
uint8_t nd_pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< ND-PMK */
uint8_t nd_pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN]; /**< ND-PMKID */
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */
uint8_t reserved: 6; /**< Reserved */
} wifi_nan_datapath_security_params_t;
} wifi_nan_security_params_t;
/**
* @brief NAN peer security material parsed from Publish/Subscribe SDF SCIA
*
* @note Per Wi-Fi Aware v4.0 §7.1.3.5, a publisher SDF SCIA may advertise
* multiple ND-PMKIDs (one per cached PMK). The receiver stores them
* and matches against locally-derived PMKID at NDP-initiation time.
* Internal only — not part of the public API. Shared between WiFi
* libraries and NAN app layer.
*/
#define NAN_PEER_MAX_PMKIDS 2 /**< Internal cap; can grow without API impact */
typedef struct {
uint16_t csid_bitmap; /**< Peer's advertised Cipher Suite ID bitmap */
uint8_t num_pmkids; /**< Number of parsed PMKIDs */
uint8_t pmkids[NAN_PEER_MAX_PMKIDS][ESP_WIFI_NAN_NDP_PMKID_LEN]; /**< Parsed ND-PMKIDs */
uint8_t group_data_prot: 1; /**< Peer advertises group data frame protection */
uint8_t group_mgmt_prot: 1; /**< Peer advertises group mgmt frame protection */
uint8_t reserved: 6; /**< Reserved */
} wifi_nan_peer_sdf_security_t;
/* NAN Peer info parsed from SDF */
struct nan_cb_peer_info {
@@ -71,7 +95,7 @@ struct nan_cb_peer_info {
uint8_t ssi_ver; /**< SSI version (service_match) */
uint8_t *ssi; /**< Service-specific information */
uint16_t ssi_len; /**< SSI length in bytes */
wifi_nan_discovery_security_params_t *peer_security_params; /**< Peer's discovery security params (cipher / PMKIDs) */
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 */
};
@@ -167,18 +191,23 @@ struct nan_secure_dp_funcs {
uint8_t ndp_id, const uint8_t *peer_nmi);
/* RX-path attribute parsers (CSIA / SCIA / key-desc). */
void (*parse_ndp_csia)(void *frm, size_t buf_len, wifi_nan_datapath_security_params_t *param);
void (*parse_ndp_scia)(void *frm, size_t buf_len, wifi_nan_datapath_security_params_t *param);
void (*parse_ndp_csia)(void *frm, size_t buf_len, wifi_nan_security_params_t *param);
void (*parse_ndp_scia)(void *frm, size_t buf_len, wifi_nan_security_params_t *param);
void (*parse_ndp_key_desc)(void *frm, size_t buf_len, uint8_t ndp_id, const uint8_t *peer_nmi);
/* Publish-side security parser (called when blob processes inbound publish SDF) */
esp_err_t (*parse_publish_security)(const uint8_t *attrs, size_t attrs_len,
wifi_nan_discovery_security_params_t *security);
wifi_nan_peer_sdf_security_t *security);
/* Publish-init helper -- derives ND-PMK / ND-PMKID from the publish cfg
* passphrase and stores them on the host service record. Result is
* unused when CONFIG_ESP_WIFI_NAN_SECURITY=n. */
esp_err_t (*derive_security_params)(wifi_nan_publish_cfg_t *cfg);
/* Derives ND-PMK + ND-PMKID for each credential in sec_cfg->creds[] and
* writes one entry per credential into out_derived[0..num_credentials).
* Caller passes service_name + the original publish/subscribe security
* cfg. out_derived must point at an array of at least
* ESP_WIFI_NAN_MAX_CREDS_PER_SVC entries. All inputs are treated as
* read-only. Returns ESP_FAIL or NULL when CONFIG_ESP_WIFI_NAN_SECURITY=n. */
esp_err_t (*derive_security_params)(const char *service_name,
const wifi_nan_discovery_security_params_t *sec_cfg,
wifi_nan_security_params_t *out_derived);
/* NDP security gate: cipher-suite bitmap for (ndp_id, peer_nmi), or 0 for open. */
uint16_t (*get_ndp_security_csid)(uint8_t ndp_id, const uint8_t *peer_nmi);

View File

@@ -868,7 +868,7 @@ typedef struct {
#define ESP_WIFI_NAN_NDP_PMK_LEN 32 /**< Length of NAN Datapath PMK */
#define ESP_WIFI_NAN_NDP_PMKID_LEN 16 /**< Length of NAN Datapath PMKID */
#define ESP_WIFI_NAN_MAX_PMKIDS 2 /**< Maximum number of PMKIDs supported */
#define ESP_WIFI_NAN_MAX_CREDS_PER_SVC 4 /**< Maximum number of NAN security credentials per service (passphrase/PMK entries) */
#define ESP_WIFI_MAX_SVC_NAME_LEN 256 /**< Maximum length of NAN service name */
#define ESP_WIFI_MAX_FILTER_LEN 256 /**< Maximum length of NAN service filter */
@@ -912,7 +912,7 @@ typedef enum {
} wifi_nan_service_type_t;
/**
* @brief NAN Cipher Suite IDs (Spec 4.1.1 & 6.1.1)
* @brief NAN Cipher Suite IDs (Wi-Fi Aware v4.0 §4.1.1 & §6.1.1)
*
* @note Only WIFI_NAN_CSID_NCS_SK_128 is currently supported by the firmware.
* The other values are reserved for future support; selecting any of
@@ -936,19 +936,37 @@ typedef enum {
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256)
/**
* @brief NAN Discovery security parameters (Spec 4.1.1 - Publish/Subscribe)
* @brief NAN security credential — one passphrase or raw PMK + the cipher it's bound to.
*
* Per Wi-Fi Aware v4.0 §7.1.3.5 the PMKID derivation formula is cipher-specific
* (NCS-SK-128 uses HMAC-SHA-256; NCS-SK-256 uses HMAC-SHA-384), so each
* credential must carry the cipher it was provisioned for.
*/
typedef struct {
uint16_t csid_bitmap; /**< Bitmap of Supported Cipher Suite IDs (WIFI_NAN_CSID_BIT_*) */
uint8_t num_pmkids; /**< Number of PMKIDs */
uint8_t pmkids[ESP_WIFI_NAN_MAX_PMKIDS][ESP_WIFI_NAN_NDP_PMKID_LEN]; /**< ND-PMKIDs */
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */
uint8_t use_pmk: 1; /**< 0 - Use passphrase, 1 - Use PMK directly */
uint8_t reserved: 5; /**< Reserved */
char passphrase[MAX_PASSPHRASE_LEN]; /**< NCS-SK passphrase (use_pmk=0). NUL-terminated. */
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< NCS-SK PMK (use_pmk=1). Raw bytes, not NUL-terminated. */
uint8_t csid; /**< Cipher Suite ID this credential is for (wifi_nan_cipher_suite_id_t value) */
uint8_t use_pmk: 1; /**< 0 - Use passphrase, 1 - Use PMK directly */
uint8_t reserved: 7; /**< Reserved */
char passphrase[MAX_PASSPHRASE_LEN]; /**< NCS-SK passphrase (use_pmk=0). NUL-terminated. */
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< NCS-SK PMK (use_pmk=1). Raw bytes, not NUL-terminated. */
} wifi_nan_credential_t;
/**
* @brief NAN Discovery security parameters (Wi-Fi Aware v4.0 §4.1.1 - Publish/Subscribe)
*
* Per Wi-Fi Aware v4.0 §9.5.21.4 (SCIA) and §7.1.3.5 the Publish/Subscribe SDF
* may advertise multiple ND-PMKIDs (one per provisioned ND-PMK). Applications
* provide one or more credentials in @c creds; the stack derives PMK + PMKID
* per credential and emits the multi-SCID list. Subscriber-side, the library
* walks an incoming publisher's SCID list and matches against any of the
* locally-provisioned credentials. The CSIA cipher bitmap advertised on air
* is computed by the stack as the union of each credential's @c csid.
*/
typedef struct {
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */
uint8_t reserved: 6; /**< Reserved */
uint8_t num_credentials; /**< Number of valid entries in @c creds (0..ESP_WIFI_NAN_MAX_CREDS_PER_SVC). 0 = open service. */
wifi_nan_credential_t creds[ESP_WIFI_NAN_MAX_CREDS_PER_SVC]; /**< Credentials list. */
} wifi_nan_discovery_security_params_t;
/**

View File

@@ -17,6 +17,8 @@ extern "C" {
#endif
#define WIFI_AP_DEFAULT_MAX_IDLE_PERIOD 292 /**< Default timeout for SoftAP BSS Max Idle. Unit: 1000TUs >**/
#define MAX_SSID_LEN 32 /**< Maximum length of SSID */
#define MAX_PASSPHRASE_LEN 64 /**< Maximum length of passphrase */
/**
* @brief Wi-Fi mode type
@@ -866,7 +868,7 @@ typedef struct {
#define ESP_WIFI_NAN_NDP_PMK_LEN 32 /**< Length of NAN Datapath PMK */
#define ESP_WIFI_NAN_NDP_PMKID_LEN 16 /**< Length of NAN Datapath PMKID */
#define ESP_WIFI_NAN_MAX_PMKIDS 2 /**< Maximum number of PMKIDs supported */
#define ESP_WIFI_NAN_MAX_CREDS_PER_SVC 4 /**< Maximum number of NAN security credentials per service (passphrase/PMK entries) */
#define ESP_WIFI_MAX_SVC_NAME_LEN 256 /**< Maximum length of NAN service name */
#define ESP_WIFI_MAX_FILTER_LEN 256 /**< Maximum length of NAN service filter */
@@ -910,7 +912,7 @@ typedef enum {
} wifi_nan_service_type_t;
/**
* @brief NAN Cipher Suite IDs (Spec 4.1.1 & 6.1.1)
* @brief NAN Cipher Suite IDs (Wi-Fi Aware v4.0 §4.1.1 & §6.1.1)
*
* @note Only WIFI_NAN_CSID_NCS_SK_128 is currently supported by the firmware.
* The other values are reserved for future support; selecting any of
@@ -934,19 +936,37 @@ typedef enum {
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256)
/**
* @brief NAN Discovery security parameters (Spec 4.1.1 - Publish/Subscribe)
* @brief NAN security credential - one passphrase or raw PMK + the cipher it's bound to.
*
* Per Wi-Fi Aware v4.0 §7.1.3.5 the PMKID derivation formula is cipher-specific
* (NCS-SK-128 uses HMAC-SHA-256; NCS-SK-256 uses HMAC-SHA-384), so each
* credential must carry the cipher it was provisioned for.
*/
typedef struct {
uint16_t csid_bitmap; /**< Bitmap of Supported Cipher Suite IDs (WIFI_NAN_CSID_BIT_*) */
uint8_t num_pmkids; /**< Number of PMKIDs */
uint8_t pmkids[ESP_WIFI_NAN_MAX_PMKIDS][ESP_WIFI_NAN_NDP_PMKID_LEN]; /**< ND-PMKIDs */
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */
uint8_t use_pmk: 1; /**< 0 - Use passphrase, 1 - Use PMK directly */
uint8_t reserved: 5; /**< Reserved */
char passphrase[MAX_PASSPHRASE_LEN]; /**< NCS-SK passphrase (use_pmk=0). NUL-terminated. */
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< NCS-SK PMK (use_pmk=1). Raw bytes, not NUL-terminated. */
uint8_t csid; /**< Cipher Suite ID this credential is for (wifi_nan_cipher_suite_id_t value) */
uint8_t use_pmk: 1; /**< 0 - Use passphrase, 1 - Use PMK directly */
uint8_t reserved: 7; /**< Reserved */
char passphrase[MAX_PASSPHRASE_LEN]; /**< NCS-SK passphrase (use_pmk=0). NUL-terminated. */
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< NCS-SK PMK (use_pmk=1). Raw bytes, not NUL-terminated. */
} wifi_nan_credential_t;
/**
* @brief NAN Discovery security parameters (Wi-Fi Aware v4.0 §4.1.1 - Publish/Subscribe)
*
* Per Wi-Fi Aware v4.0 §9.5.21.4 (SCIA) and §7.1.3.5 the Publish/Subscribe SDF
* may advertise multiple ND-PMKIDs (one per provisioned ND-PMK). Applications
* provide one or more credentials in @c creds; the stack derives PMK + PMKID
* per credential and emits the multi-SCID list. Subscriber-side, the library
* walks an incoming publisher's SCID list and matches against any of the
* locally-provisioned credentials. The CSIA cipher bitmap advertised on air
* is computed by the stack as the union of each credential's @c csid.
*/
typedef struct {
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */
uint8_t reserved: 6; /**< Reserved */
uint8_t num_credentials; /**< Number of valid entries in @c creds (0..ESP_WIFI_NAN_MAX_CREDS_PER_SVC). 0 = open service. */
wifi_nan_credential_t creds[ESP_WIFI_NAN_MAX_CREDS_PER_SVC]; /**< Credentials list. */
} wifi_nan_discovery_security_params_t;
/**
@@ -1290,8 +1310,6 @@ typedef enum {
WPS_FAIL_REASON_MAX /**< Max WPS fail reason */
} wifi_event_sta_wps_fail_reason_t;
#define MAX_SSID_LEN 32 /**< Maximum length of SSID */
#define MAX_PASSPHRASE_LEN 64 /**< Maximum length of passphrase */
#define MAX_WPS_AP_CRED 3 /**< Maximum number of AP credentials received from WPS handshake */
/**

View File

@@ -111,7 +111,7 @@ struct own_svc_info *nan_find_own_svc(uint8_t svc_id)
return p_svc;
}
static struct own_svc_info *nan_find_own_svc_by_name(const char *svc_name)
struct own_svc_info *nan_find_own_svc_by_name(const char *svc_name)
{
struct own_svc_info *p_svc = NULL;
@@ -271,46 +271,68 @@ static bool nan_services_limit_reached(void)
return true;
}
static void nan_record_own_svc(uint8_t id, uint8_t type, const char svc_name[],
bool ndp_resp_needed,
const wifi_nan_discovery_security_params_t *security_cfg)
/* Pre-claim a slot for an upcoming publish/subscribe service. The slot is
* marked with a pending sentinel svc_id (0xFF) so nan_find_own_svc_by_name()
* can find it from the derive callback running on the WiFi task — that's how
* the host's per-credential derived_security[] mirror gets populated without
* a duplicate PBKDF2 pass on the app/main task (which would block IDLE0 and
* trip the watchdog). Real svc_id is stamped later by nan_finalize_own_svc(). */
#define NAN_SVC_ID_PENDING 0xFF
static struct own_svc_info *nan_claim_own_svc_slot(uint8_t type, const char svc_name[],
const wifi_nan_discovery_security_params_t *security_cfg)
{
struct own_svc_info *p_svc = NULL;
for (int i = 0; i < ESP_WIFI_NAN_MAX_SVC_SUPPORTED; i++) {
if (s_nan_ctx.own_svc[i].svc_id == 0) {
p_svc = &s_nan_ctx.own_svc[i];
break;
}
}
if (!p_svc) {
return;
return NULL;
}
p_svc->svc_id = id;
p_svc->svc_id = NAN_SVC_ID_PENDING;
p_svc->type = type;
strlcpy(p_svc->svc_name, svc_name, ESP_WIFI_MAX_SVC_NAME_LEN);
SLIST_INIT(&(p_svc->peer_list));
if (type == ESP_NAN_PUBLISH) {
p_svc->ndp_resp_needed = ndp_resp_needed;
}
SLIST_INIT(&p_svc->peer_list);
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
/* Wipe to drop stale PMK material if this slot was previously used. */
forced_memzero(&p_svc->security_cfg, sizeof(p_svc->security_cfg));
forced_memzero(p_svc->pmk_cache, sizeof(p_svc->pmk_cache));
forced_memzero(&p_svc->user_cfg, sizeof(p_svc->user_cfg));
forced_memzero(&p_svc->derived_security, sizeof(p_svc->derived_security));
if (security_cfg) {
memcpy(&p_svc->security_cfg, security_cfg, sizeof(wifi_nan_discovery_security_params_t));
if (security_cfg->num_pmkids > 0) {
memcpy(p_svc->pmk_cache[0], security_cfg->pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
/* Public struct has one pmk[32]; cap num_pmkids to the count we cached. */
p_svc->security_cfg.num_pmkids = 1;
}
memcpy(&p_svc->user_cfg, security_cfg, sizeof(*security_cfg));
}
#else
(void)security_cfg;
#endif /* CONFIG_ESP_WIFI_NAN_SECURITY */
#endif
return p_svc;
}
/* Stamp the real svc_id and per-publish flags after the blob accepts the
* service. Looked up by name since the WiFi-task derive callback may have
* already populated derived_security[] before this runs. */
static void nan_finalize_own_svc(const char *svc_name, uint8_t id, bool ndp_resp_needed)
{
struct own_svc_info *p_svc = nan_find_own_svc_by_name(svc_name);
if (!p_svc) {
return;
}
p_svc->svc_id = id;
if (p_svc->type == ESP_NAN_PUBLISH) {
p_svc->ndp_resp_needed = ndp_resp_needed;
}
}
/* Release a pending slot when the blob's publish/subscribe call fails. */
static void nan_abort_own_svc(const char *svc_name)
{
struct own_svc_info *p_svc = nan_find_own_svc_by_name(svc_name);
if (!p_svc) {
return;
}
forced_memzero(p_svc, sizeof(*p_svc));
/* svc_id back to 0 -> slot free */
}
/* A slot is in use once nan_record_new_ndl/preclaim has stamped peer_nmi,
@@ -566,9 +588,21 @@ void nan_app_service_match_cb(uint8_t sub_id, struct nan_cb_peer_info *peer_info
ESP_LOGI(TAG, "Service matched with capabilities: 0x%04x", capab);
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
if (peer_info->peer_security_params) {
/* Service-match security gate:
* 1. Subscriber configured no credentials (open subscribe) -> accept
* regardless of what the peer advertised. NDP will be open.
* 2. Subscriber configured credentials (secure subscribe) -> peer
* must have advertised PMKIDs AND at least one of them must match
* one of the local creds, else drop. */
if (nan_security_subscriber_has_creds()) {
if (!peer_info->peer_security_params ||
peer_info->peer_security_params->num_pmkids == 0) {
ESP_LOGW(TAG, "Secure subscribe: peer "MACSTR" advertises no PMKIDs",
MAC2STR(pub_mac));
return;
}
if (!nan_security_service_match(pub_mac, peer_info->peer_security_params)) {
ESP_LOGD(TAG, "PMKID mismatch with "MACSTR, MAC2STR(pub_mac));
ESP_LOGW(TAG, "PMKID mismatch with "MACSTR, MAC2STR(pub_mac));
return;
}
}
@@ -1382,10 +1416,20 @@ uint8_t esp_wifi_nan_publish_service(const wifi_nan_publish_cfg_t *publish_cfg)
ESP_LOGE(TAG, "Encrypted datapath not enabled (CONFIG_ESP_WIFI_NAN_SECURITY)");
goto fail;
#else
if (!(publish_cfg->security_cfg.csid_bitmap & WIFI_NAN_CSID_BIT_NCS_SK_128)) {
ESP_LOGE(TAG, "Unsupported cipher suite in csid_bitmap (only NCS-SK-128 is supported)");
if (publish_cfg->security_cfg.num_credentials == 0 ||
publish_cfg->security_cfg.num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) {
ESP_LOGE(TAG, "security_cfg.num_credentials=%u must be 1..%u",
publish_cfg->security_cfg.num_credentials,
ESP_WIFI_NAN_MAX_CREDS_PER_SVC);
goto fail;
}
for (uint8_t i = 0; i < publish_cfg->security_cfg.num_credentials; i++) {
uint8_t csid = publish_cfg->security_cfg.creds[i].csid;
if (csid != WIFI_NAN_CSID_NCS_SK_128) {
ESP_LOGE(TAG, "creds[%u].csid=%u unsupported (only NCS-SK-128)", i, csid);
goto fail;
}
}
#endif
}
@@ -1397,24 +1441,31 @@ uint8_t esp_wifi_nan_publish_service(const wifi_nan_publish_cfg_t *publish_cfg)
}
memcpy(cfg, publish_cfg, sizeof(*cfg));
/* Security derivation (PMK, PMKID) now runs in WiFi task context —
* the WiFi library calls nan_derive_security_params(cfg) during publish processing.
* After esp_nan_internal_publish_service returns, cfg->security_cfg has
* the derived PMK and PMKID populated by the WiFi task. */
/* Pre-claim host slot BEFORE calling into the blob. Reason: the blob
* invokes our derive_security_params callback on the WiFi task and looks
* up the host's slot by service name to mirror derived ND-PMK/ND-PMKID
* into p_svc->derived_security[]. Doing the derive on WiFi task (not the
* app/main task) keeps PBKDF2's hardware-SHA polling off IDLE0 and avoids
* tripping the task watchdog when num_credentials > 1. */
if (!nan_claim_own_svc_slot(ESP_NAN_PUBLISH, publish_cfg->service_name,
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
&cfg->security_cfg
#else
NULL
#endif
)) {
ESP_LOGE(TAG, "No free service slot");
goto fail;
}
if (esp_nan_internal_publish_service(cfg, (uint8_t *) &pub_id, false) != ESP_OK) {
ESP_LOGE(TAG, "Failed to publish service '%s'", publish_cfg->service_name);
nan_abort_own_svc(publish_cfg->service_name);
goto fail;
}
ESP_LOGI(TAG, "Started Publishing %s [Service ID - %u]", publish_cfg->service_name, pub_id);
nan_record_own_svc(pub_id, ESP_NAN_PUBLISH, publish_cfg->service_name, publish_cfg->ndp_resp_needed,
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
&cfg->security_cfg
#else
NULL
#endif
);
nan_finalize_own_svc(publish_cfg->service_name, pub_id, publish_cfg->ndp_resp_needed);
os_free(cfg);
NAN_DATA_UNLOCK();
@@ -1481,10 +1532,20 @@ uint8_t esp_wifi_nan_subscribe_service(const wifi_nan_subscribe_cfg_t *subscribe
ESP_LOGE(TAG, "Encrypted datapath not enabled (CONFIG_ESP_WIFI_NAN_SECURITY)");
return 0;
#else
if (!(subscribe_cfg->security_cfg.csid_bitmap & WIFI_NAN_CSID_BIT_NCS_SK_128)) {
ESP_LOGE(TAG, "Unsupported cipher suite in csid_bitmap (only NCS-SK-128 is supported)");
if (subscribe_cfg->security_cfg.num_credentials == 0 ||
subscribe_cfg->security_cfg.num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) {
ESP_LOGE(TAG, "security_cfg.num_credentials=%u must be 1..%u",
subscribe_cfg->security_cfg.num_credentials,
ESP_WIFI_NAN_MAX_CREDS_PER_SVC);
return 0;
}
for (uint8_t i = 0; i < subscribe_cfg->security_cfg.num_credentials; i++) {
uint8_t csid = subscribe_cfg->security_cfg.creds[i].csid;
if (csid != WIFI_NAN_CSID_NCS_SK_128) {
ESP_LOGE(TAG, "creds[%u].csid=%u unsupported (only NCS-SK-128)", i, csid);
return 0;
}
}
#endif
}
NAN_DATA_LOCK();
@@ -1502,14 +1563,22 @@ uint8_t esp_wifi_nan_subscribe_service(const wifi_nan_subscribe_cfg_t *subscribe
goto fail;
}
/* Pre-claim host slot BEFORE the blob's subscribe call; see comment on
* the publish path for the watchdog rationale. */
if (!nan_claim_own_svc_slot(ESP_NAN_SUBSCRIBE, subscribe_cfg->service_name,
&subscribe_cfg->security_cfg)) {
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);
goto fail;
}
ESP_LOGI(TAG, "Started Subscribing to %s [Service ID - %u]", subscribe_cfg->service_name, sub_id);
nan_record_own_svc((uint8_t) sub_id, ESP_NAN_SUBSCRIBE, subscribe_cfg->service_name, false, &subscribe_cfg->security_cfg);
nan_finalize_own_svc(subscribe_cfg->service_name, (uint8_t) sub_id, false);
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
nan_security_cache_subscriber_params(subscribe_cfg->service_name, &subscribe_cfg->security_cfg);
#endif

View File

@@ -162,8 +162,12 @@ struct own_svc_info {
bool ndp_resp_needed;
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
wifi_nan_discovery_security_params_t security_cfg;
uint8_t pmk_cache[ESP_WIFI_NAN_MAX_PMKIDS][ESP_WIFI_NAN_NDP_PMK_LEN];
/* App-provided input (creds[] / csid_bitmap / group prot flags). */
wifi_nan_discovery_security_params_t user_cfg;
/* Derived material — one ND-PMK + ND-PMKID per credential. Mirrors the
* per-service array the blob caches in svc_entry->self_security_params[].
* Valid entries: [0, user_cfg.num_credentials). */
wifi_nan_security_params_t derived_security[ESP_WIFI_NAN_MAX_CREDS_PER_SVC];
#endif
uint8_t num_peer_records;
SLIST_HEAD(peer_list_t, peer_svc_info) peer_list;
@@ -178,7 +182,7 @@ struct ndl_info {
uint8_t own_role;
uint32_t device_caps;
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
wifi_nan_datapath_security_params_t security_ctx;
wifi_nan_security_params_t security_ctx;
uint8_t anonce[NAN_NONCE_LEN];
uint8_t snonce[NAN_NONCE_LEN];
@@ -233,6 +237,7 @@ extern nan_ctx_t s_nan_ctx;
/* Helpers defined in nan_app.c, used by nan_security.c */
struct own_svc_info *nan_find_own_svc(uint8_t svc_id);
struct own_svc_info *nan_find_own_svc_by_name(const char *svc_name);
struct ndl_info *nan_find_ndl(uint8_t ndp_id, uint8_t peer_nmi[]);
struct ndl_info *nan_find_ndl_by_pub_id_and_peer(uint8_t pub_id, const uint8_t *peer_nmi);
@@ -291,12 +296,12 @@ int esp_nan_verify_ndp_security_install_mic(uint8_t *m4_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
void esp_nan_parse_ndp_csia(void *frm, size_t buf_len, wifi_nan_datapath_security_params_t *param);
void esp_nan_parse_ndp_scia(void *frm, size_t buf_len, wifi_nan_datapath_security_params_t *param);
void esp_nan_parse_ndp_csia(void *frm, size_t buf_len, wifi_nan_security_params_t *param);
void esp_nan_parse_ndp_scia(void *frm, size_t buf_len, wifi_nan_security_params_t *param);
void esp_nan_parse_ndp_key_desc(void *frm, size_t buf_len, uint8_t ndp_id, const uint8_t *peer_nmi);
esp_err_t esp_nan_parse_publish_security(const uint8_t *attrs, size_t attrs_len,
wifi_nan_discovery_security_params_t *security);
wifi_nan_peer_sdf_security_t *security);
/* Helpers defined in nan_security.c, used by nan_app.c */
@@ -311,8 +316,12 @@ void nan_security_apply_pending(struct ndl_info *ndl,
const uint8_t *peer_nmi,
const uint8_t *peer_ndi);
/* PMK / PMKID derivation entry point used by the publish path. */
esp_err_t nan_derive_security_params(wifi_nan_publish_cfg_t *cfg);
/* PMK / PMKID derivation entry point used by the publish path. Derives from
* (service_name, sec_cfg) (passphrase or PMK) and writes the result into
* out_derived. */
esp_err_t nan_derive_security_params(const char *service_name,
const wifi_nan_discovery_security_params_t *sec_cfg,
wifi_nan_security_params_t *out_derived);
/* Blob-side gate query: returns ndl->security_ctx.csid_bitmap for the NDP
* keyed on (ndp_id, peer_nmi), or 0 if no NDL match. ndp_id=0 is valid for
@@ -336,13 +345,23 @@ esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl,
* from the service-match callback.
*/
bool nan_security_service_match(const uint8_t *publisher_nmi,
const wifi_nan_discovery_security_params_t *peer_sec);
const wifi_nan_peer_sdf_security_t *peer_sec);
/* Returns true if the local subscriber configured at least one credential
* via the most recent esp_wifi_nan_subscribe_service(). Used by the
* service-match gate to decide whether to PMKID-validate an incoming peer. */
bool nan_security_subscriber_has_creds(void);
#else
static inline esp_err_t nan_derive_security_params(wifi_nan_publish_cfg_t *cfg)
static inline esp_err_t nan_derive_security_params(const char *service_name,
const wifi_nan_discovery_security_params_t *sec_cfg,
wifi_nan_security_params_t *out_derived)
{
(void)cfg;
(void)service_name;
(void)sec_cfg;
(void)out_derived;
return ESP_FAIL;
}
static inline bool nan_security_subscriber_has_creds(void) { return false; }
#endif /* CONFIG_ESP_WIFI_NAN_SECURITY */
#ifdef __cplusplus

View File

@@ -70,6 +70,71 @@ static struct {
wifi_nan_discovery_security_params_t security_cfg;
} s_nan_subscriber_sec_cache;
/* Per-peer subscriber match memory: at service-match time
* nan_security_service_match identifies which local credential matched the
* publisher; we remember (publisher NMI -> matched cred index) so that the
* later NDP-init path (nan_security_populate_initiator_ndl) derives M1's pair
* PMKID from the *correct* credential instead of always cred[0]. Sized at
* NAN_MAX_PEERS_RECORD to cover the realistic peer count; round-robin
* eviction on overflow. */
#define NAN_SUB_MATCH_CACHE_SIZE NAN_MAX_PEERS_RECORD
static struct {
bool used;
uint8_t peer_nmi[ETH_ALEN];
uint8_t matched_cred_idx;
} s_nan_subscriber_match_cache[NAN_SUB_MATCH_CACHE_SIZE];
static uint8_t s_nan_subscriber_match_cursor;
static void nan_subscriber_match_remember(const uint8_t *peer_nmi, uint8_t cred_idx)
{
if (!peer_nmi) {
return;
}
/* Update existing entry for this peer if present. */
for (uint8_t i = 0; i < NAN_SUB_MATCH_CACHE_SIZE; i++) {
if (s_nan_subscriber_match_cache[i].used &&
memcmp(s_nan_subscriber_match_cache[i].peer_nmi, peer_nmi, ETH_ALEN) == 0) {
s_nan_subscriber_match_cache[i].matched_cred_idx = cred_idx;
return;
}
}
/* First free slot. */
for (uint8_t i = 0; i < NAN_SUB_MATCH_CACHE_SIZE; i++) {
if (!s_nan_subscriber_match_cache[i].used) {
s_nan_subscriber_match_cache[i].used = true;
memcpy(s_nan_subscriber_match_cache[i].peer_nmi, peer_nmi, ETH_ALEN);
s_nan_subscriber_match_cache[i].matched_cred_idx = cred_idx;
return;
}
}
/* Full: round-robin evict. */
uint8_t idx = s_nan_subscriber_match_cursor;
s_nan_subscriber_match_cursor = (uint8_t)((s_nan_subscriber_match_cursor + 1) % NAN_SUB_MATCH_CACHE_SIZE);
s_nan_subscriber_match_cache[idx].used = true;
memcpy(s_nan_subscriber_match_cache[idx].peer_nmi, peer_nmi, ETH_ALEN);
s_nan_subscriber_match_cache[idx].matched_cred_idx = cred_idx;
}
static int nan_subscriber_match_recall(const uint8_t *peer_nmi)
{
if (!peer_nmi) {
return -1;
}
for (uint8_t i = 0; i < NAN_SUB_MATCH_CACHE_SIZE; i++) {
if (s_nan_subscriber_match_cache[i].used &&
memcmp(s_nan_subscriber_match_cache[i].peer_nmi, peer_nmi, ETH_ALEN) == 0) {
return (int)s_nan_subscriber_match_cache[i].matched_cred_idx;
}
}
return -1;
}
static void nan_subscriber_match_clear(void)
{
memset(s_nan_subscriber_match_cache, 0, sizeof(s_nan_subscriber_match_cache));
s_nan_subscriber_match_cursor = 0;
}
/*-------------------------------------------------------------------------
* Static helpers
*-----------------------------------------------------------------------*/
@@ -98,22 +163,6 @@ static const uint8_t *nan_find_attr(const uint8_t *attrs, size_t attrs_len,
return NULL;
}
/*
* Pick the highest-priority cipher suite the local device implements from
* the negotiated CSIA bitmap. Only NCS-SK-128 is wired through the rest of
* the M1M4 path today (PTK length, MIC length, KCK/KEK lengths in
* nan_i.h are 128-bit-specific); 256-bit and PK suites are intentionally
* not selected here — return 0 to signal "no supported suite" so callers
* fail negotiation rather than dispatching to a half-implemented path.
*/
static uint8_t nan_get_first_csid(uint16_t csid_bitmap)
{
if (csid_bitmap & WIFI_NAN_CSID_BIT_NCS_SK_128) {
return WIFI_NAN_CSID_NCS_SK_128;
}
return 0;
}
/*
* Service ID = first 6 bytes of SHA256(lowercase(service_name))
* per Wi-Fi Aware v4.0 §5.1.5 (Service Name and Service ID).
@@ -211,25 +260,22 @@ static bool nan_derive_ndp_request_pmkid(const uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_
}
/*
* Match peer's ND-PMKID (from NDP Request/SCIA) against our PMK cache.
* Match peer's ND-PMKID (from NDP Request/SCIA) against our per-credential
* PMK cache. Returns the index in p_svc->derived_security[] that matched, or
* -1 if no credential matches.
*
* Rule: Do NOT compare NDP Request PMKID to Publish-PMKID — they differ by design.
* - Publish SCIA PMKID uses IAddr = FF:FF:FF:FF:FF:FF (initiator-independent).
* - NDP Request SCIA PMKID uses Initiator NMI and Responder NMI (pair-specific).
*/
static bool nan_match_pmkid(struct own_svc_info *p_svc,
const uint8_t peer_pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN],
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN],
const uint8_t *peer_nmi,
const uint8_t *peer_ndi)
static int nan_match_pmkid(struct own_svc_info *p_svc,
const uint8_t peer_pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN],
const uint8_t *peer_nmi,
const uint8_t *peer_ndi)
{
(void)peer_ndi;
if (!p_svc || !peer_pmkid) {
return false;
}
if (!peer_nmi || !p_svc->svc_name[0]) {
if (!p_svc || !peer_pmkid || !peer_nmi || !p_svc->svc_name[0]) {
goto no_match;
}
@@ -248,40 +294,30 @@ static bool nan_match_pmkid(struct own_svc_info *p_svc,
ESP_LOGD(TAG, " Initiator NMI (peer): "MACSTR, MAC2STR(peer_nmi));
ESP_LOGD(TAG, " Responder NMI (us): "MACSTR, MAC2STR(our_nmi));
/* Cap loop at array bound; guards against unsanitized num_pmkids. */
uint8_t cached = p_svc->security_cfg.num_pmkids;
if (cached > ESP_WIFI_NAN_MAX_PMKIDS) {
cached = ESP_WIFI_NAN_MAX_PMKIDS;
}
for (int i = 0; i < cached; i++) {
for (uint8_t i = 0; i < p_svc->user_cfg.num_credentials; i++) {
uint8_t expected_pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN];
const uint8_t *own_pmk = p_svc->derived_security[i].nd_pmk;
/* Spec order: Initiator NMI, Responder NMI */
if (nan_derive_ndp_request_pmkid(p_svc->pmk_cache[i], peer_nmi, our_nmi,
service_id, expected_pmkid)) {
if (memcmp(expected_pmkid, peer_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN) == 0) {
if (pmk) {
memcpy(pmk, p_svc->pmk_cache[i], ESP_WIFI_NAN_NDP_PMK_LEN);
}
return true;
}
if (nan_derive_ndp_request_pmkid(own_pmk, peer_nmi, our_nmi,
service_id, expected_pmkid) &&
memcmp(expected_pmkid, peer_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN) == 0) {
ESP_LOGD(TAG, "PMKID matched at credential slot %u", i);
return (int)i;
}
/* Fallback: try reversed order in case peer implementation differs */
if (nan_derive_ndp_request_pmkid(p_svc->pmk_cache[i], our_nmi, peer_nmi,
service_id, expected_pmkid)) {
if (memcmp(expected_pmkid, peer_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN) == 0) {
if (pmk) {
memcpy(pmk, p_svc->pmk_cache[i], ESP_WIFI_NAN_NDP_PMK_LEN);
}
return true;
}
if (nan_derive_ndp_request_pmkid(own_pmk, our_nmi, peer_nmi,
service_id, expected_pmkid) &&
memcmp(expected_pmkid, peer_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN) == 0) {
ESP_LOGD(TAG, "PMKID matched at credential slot %u (reversed)", i);
return (int)i;
}
}
no_match:
ESP_LOGW(TAG, "PMKID not found in our cache (no match)");
return false;
return -1;
}
/*
@@ -693,69 +729,101 @@ static uint8_t nan_keyinfo_to_msg_type(uint16_t key_info)
* Public API: PMK/PMKID derivation for publish
*-----------------------------------------------------------------------*/
esp_err_t nan_derive_security_params(wifi_nan_publish_cfg_t *cfg)
esp_err_t nan_derive_security_params(const char *service_name,
const wifi_nan_discovery_security_params_t *sec_cfg,
wifi_nan_security_params_t *out_derived)
{
uint8_t pmk[32];
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN];
uint8_t service_id[6];
uint8_t pmkid[16];
uint8_t hash[32];
esp_err_t ret = ESP_FAIL;
/* 1. Service ID = SHA256(lowercase service name)[:6] */
if (!nan_compute_service_id(cfg->service_name, service_id)) {
ESP_LOGE(TAG, "Service ID derivation failed");
goto cleanup;
if (!service_name || !sec_cfg || !out_derived) {
return ESP_ERR_INVALID_ARG;
}
if (sec_cfg->num_credentials == 0 ||
sec_cfg->num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) {
ESP_LOGE(TAG, "Invalid num_credentials=%u (cap %u)",
sec_cfg->num_credentials, ESP_WIFI_NAN_MAX_CREDS_PER_SVC);
return ESP_ERR_INVALID_ARG;
}
ESP_LOGD(TAG, "Security derivation: svc='%s'", cfg->service_name);
memset(out_derived, 0,
sizeof(*out_derived) * ESP_WIFI_NAN_MAX_CREDS_PER_SVC);
if (!nan_compute_service_id(service_name, service_id)) {
ESP_LOGE(TAG, "Service ID derivation failed");
return ESP_FAIL;
}
if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) {
ESP_LOGE(TAG, "hmac_sha256_vector not registered");
return ESP_FAIL;
}
ESP_LOGD(TAG, "Security derivation: svc='%s' num_creds=%u",
service_name, sec_cfg->num_credentials);
ESP_LOG_BUFFER_HEXDUMP(TAG, service_id, 6, ESP_LOG_DEBUG);
/* 2. PMK from passphrase (or directly provided) */
if (cfg->security_cfg.use_pmk) {
memcpy(pmk, cfg->security_cfg.pmk, 32);
} else {
uint8_t publisher_nmi[ETH_ALEN];
uint8_t csid = nan_get_first_csid(cfg->security_cfg.csid_bitmap);
if (csid == 0) {
ESP_LOGE(TAG, "No cipher suite in bitmap");
goto cleanup;
}
esp_wifi_get_mac(WIFI_IF_NAN, publisher_nmi);
if (nan_derive_nd_pmk_from_passphrase(cfg->security_cfg.passphrase, csid,
service_id, publisher_nmi, pmk) != 0) {
ESP_LOGE(TAG, "PBKDF2 ND-PMK derivation failed");
goto cleanup;
}
}
/* 3. Publish PMKID uses IAddr = ff:ff:ff:ff:ff:ff (initiator-independent).
* Pair-specific PMKID for NDP Request is derived separately. */
/* Publish-side PMKID uses IAddr=ff:ff:ff:ff:ff:ff (initiator-independent);
* pair-specific PMKID for NDP Request derived separately. */
uint8_t i_addr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t r_addr[6];
esp_wifi_get_mac(WIFI_IF_NAN, r_addr);
if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) {
ESP_LOGE(TAG, "hmac_sha256_vector not registered");
goto cleanup;
for (uint8_t i = 0; i < sec_cfg->num_credentials; i++) {
const wifi_nan_credential_t *cred = &sec_cfg->creds[i];
if (cred->csid == 0) {
ESP_LOGE(TAG, "Cred[%u] missing csid", i);
goto fail;
}
if (cred->use_pmk) {
memcpy(pmk, cred->pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
} else {
if (nan_derive_nd_pmk_from_passphrase(cred->passphrase, cred->csid,
service_id, r_addr, pmk) != 0) {
ESP_LOGE(TAG, "Cred[%u]: PBKDF2 ND-PMK derivation failed", i);
goto fail;
}
}
const unsigned char *addr_pmkid[4] = {(const unsigned char *)NAN_PMK_NAME_LABEL,
i_addr, r_addr, service_id
};
int len_pmkid[4] = {NAN_PMK_NAME_LABEL_LEN, 6, 6, 6};
g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(pmk, ESP_WIFI_NAN_NDP_PMK_LEN,
4, addr_pmkid, len_pmkid, hash);
out_derived[i].type = WIFI_NAN_SECURITY_ENCRYPTED;
out_derived[i].csid_bitmap = (uint16_t)(1u << cred->csid);
out_derived[i].group_data_prot = sec_cfg->group_data_prot;
out_derived[i].group_mgmt_prot = sec_cfg->group_mgmt_prot;
memcpy(out_derived[i].nd_pmk, pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
memcpy(out_derived[i].nd_pmkid, hash, ESP_WIFI_NAN_NDP_PMKID_LEN);
forced_memzero(pmk, sizeof(pmk));
}
const unsigned char *addr_pmkid[4] = {(const unsigned char *)NAN_PMK_NAME_LABEL,
i_addr, r_addr, service_id
};
int len_pmkid[4] = {NAN_PMK_NAME_LABEL_LEN, 6, 6, 6};
/* Mirror the derived material into the host's own_svc_info slot for this
* service (claimed before the blob's publish/subscribe call). This lets
* host paths — nan_match_pmkid (responder M1) and the NDL seeding at
* nan_app.c — read derived ND-PMK + PMKID without a second PBKDF2 pass
* on the app/main task. Single derive, two destinations. */
{
struct own_svc_info *p_svc = nan_find_own_svc_by_name(service_name);
if (p_svc) {
memcpy(p_svc->derived_security, out_derived,
sizeof(*out_derived) * sec_cfg->num_credentials);
}
}
return ESP_OK;
g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(pmk, 32, 4, addr_pmkid, len_pmkid, hash);
memcpy(pmkid, hash, 16);
memcpy(cfg->security_cfg.pmkids[0], pmkid, 16);
cfg->security_cfg.num_pmkids = 1;
memcpy(cfg->security_cfg.pmk, pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
ret = ESP_OK;
cleanup:
fail:
forced_memzero(pmk, sizeof(pmk));
return ret;
memset(out_derived, 0,
sizeof(*out_derived) * ESP_WIFI_NAN_MAX_CREDS_PER_SVC);
return ESP_FAIL;
}
/*
@@ -781,6 +849,10 @@ uint16_t nan_get_ndp_security_csid(uint8_t ndp_id, const uint8_t *peer_nmi)
void nan_security_cache_subscriber_params(const char *service_name,
const wifi_nan_discovery_security_params_t *security_cfg)
{
/* New (or cleared) subscribe session: wipe any stale per-peer match
* memory from a previous subscriber. */
nan_subscriber_match_clear();
if (!service_name || !security_cfg) {
s_nan_subscriber_sec_cache.valid = false;
return;
@@ -792,43 +864,38 @@ void nan_security_cache_subscriber_params(const char *service_name,
s_nan_subscriber_sec_cache.valid = true;
}
bool nan_security_service_match(const uint8_t *publisher_nmi,
const wifi_nan_discovery_security_params_t *peer_sec)
bool nan_security_subscriber_has_creds(void)
{
uint8_t pmk[32];
return s_nan_subscriber_sec_cache.valid &&
s_nan_subscriber_sec_cache.security_cfg.num_credentials > 0;
}
bool nan_security_service_match(const uint8_t *publisher_nmi,
const wifi_nan_peer_sdf_security_t *peer_sec)
{
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN];
uint8_t service_id[6];
uint8_t pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN];
uint8_t hash[32];
bool matched = false;
if (!s_nan_subscriber_sec_cache.valid || !publisher_nmi || !peer_sec) {
goto cleanup;
return false;
}
if (peer_sec->num_pmkids == 0) {
goto cleanup;
return false;
}
if (!nan_compute_service_id(s_nan_subscriber_sec_cache.service_name, service_id)) {
goto cleanup;
return false;
}
if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) {
return false;
}
const wifi_nan_discovery_security_params_t *cfg = &s_nan_subscriber_sec_cache.security_cfg;
if (cfg->use_pmk) {
memcpy(pmk, cfg->pmk, sizeof(pmk));
} else {
uint8_t csid = nan_get_first_csid(cfg->csid_bitmap);
if (csid == 0) {
goto cleanup;
}
if (nan_derive_nd_pmk_from_passphrase(cfg->passphrase, csid, service_id,
publisher_nmi, pmk) != 0) {
goto cleanup;
}
}
if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) {
goto cleanup;
if (cfg->num_credentials == 0 ||
cfg->num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) {
return false;
}
uint8_t i_addr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
@@ -837,19 +904,45 @@ bool nan_security_service_match(const uint8_t *publisher_nmi,
};
int len_pmkid[4] = {NAN_PMK_NAME_LABEL_LEN, 6, 6, 6};
g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(pmk, 32, 4, addr_pmkid, len_pmkid, hash);
memcpy(pmkid, hash, ESP_WIFI_NAN_NDP_PMKID_LEN);
/* Cross-product: each local credential vs each peer-advertised PMKID. First
* match wins. Both sides honor the spec PMKID formula with IAddr broadcast,
* RAddr = publisher_nmi, so a (local cred, peer SCID) match indicates a
* shared ND-PMK for this service. */
for (uint8_t c = 0; c < cfg->num_credentials; c++) {
const wifi_nan_credential_t *cred = &cfg->creds[c];
if (cred->csid == 0) {
continue;
}
for (unsigned int i = 0; i < peer_sec->num_pmkids && i < ESP_WIFI_NAN_MAX_PMKIDS; i++) {
if (memcmp(pmkid, peer_sec->pmkids[i], ESP_WIFI_NAN_NDP_PMKID_LEN) == 0) {
ESP_LOGD(TAG, "ND-PMKID match with publisher " MACSTR, MAC2STR(publisher_nmi));
ESP_LOG_BUFFER_HEXDUMP(TAG, pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN, ESP_LOG_INFO);
matched = true;
break;
if (cred->use_pmk) {
memcpy(pmk, cred->pmk, sizeof(pmk));
} else {
if (nan_derive_nd_pmk_from_passphrase(cred->passphrase, cred->csid,
service_id, publisher_nmi, pmk) != 0) {
continue;
}
}
g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(pmk, ESP_WIFI_NAN_NDP_PMK_LEN,
4, addr_pmkid, len_pmkid, hash);
memcpy(pmkid, hash, ESP_WIFI_NAN_NDP_PMKID_LEN);
for (uint8_t i = 0; i < peer_sec->num_pmkids && i < NAN_PEER_MAX_PMKIDS; i++) {
if (memcmp(pmkid, peer_sec->pmkids[i], ESP_WIFI_NAN_NDP_PMKID_LEN) == 0) {
ESP_LOGD(TAG, "ND-PMKID match: cred[%u] vs peer SCID[%u], publisher "MACSTR,
c, i, MAC2STR(publisher_nmi));
ESP_LOG_BUFFER_HEXDUMP(TAG, pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN, ESP_LOG_INFO);
/* Remember which local cred matched this publisher so the
* later NDP-init path (nan_security_populate_initiator_ndl)
* uses the right cred for M1's pair-PMKID derivation. */
nan_subscriber_match_remember(publisher_nmi, c);
matched = true;
goto done;
}
}
}
cleanup:
done:
forced_memzero(pmk, sizeof(pmk));
return matched;
}
@@ -875,10 +968,24 @@ esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl,
return ESP_OK; /* subscriber didn't cache security; open NDP */
}
const wifi_nan_discovery_security_params_t *cfg = &s_nan_subscriber_sec_cache.security_cfg;
if (cfg->csid_bitmap == 0) {
if (cfg->num_credentials == 0) {
return ESP_OK; /* subscriber didn't request encrypted datapath */
}
/* Use the credential that matched this publisher at discovery time
* (remembered by nan_security_service_match -> nan_subscriber_match_remember).
* Falls back to creds[0] only when no match memory exists for this peer
* (e.g., subscriber initiated NDP without a prior service match — atypical). */
int matched_idx = nan_subscriber_match_recall(peer_nmi);
uint8_t cred_idx = (matched_idx >= 0) ? (uint8_t)matched_idx : 0;
if (cred_idx >= cfg->num_credentials) {
cred_idx = 0;
}
const wifi_nan_credential_t *cred = &cfg->creds[cred_idx];
ESP_LOGD(TAG, "NDP req security: using cred[%u] for peer "MACSTR
" (matched_idx=%d, num_creds=%u)",
cred_idx, MAC2STR(peer_nmi), matched_idx, cfg->num_credentials);
uint8_t pmk[ESP_WIFI_NAN_NDP_PMK_LEN];
uint8_t service_id[6];
uint8_t our_nmi[6];
@@ -893,14 +1000,13 @@ esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl,
goto cleanup;
}
if (cfg->use_pmk) {
memcpy(pmk, cfg->pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
if (cred->use_pmk) {
memcpy(pmk, cred->pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
} else {
uint8_t csid = nan_get_first_csid(cfg->csid_bitmap);
if (csid == 0) {
if (cred->csid == 0) {
goto cleanup;
}
if (nan_derive_nd_pmk_from_passphrase(cfg->passphrase, csid, service_id,
if (nan_derive_nd_pmk_from_passphrase(cred->passphrase, cred->csid, service_id,
peer_nmi, pmk) != 0) {
ESP_LOGE(TAG, "NDP req security: passphrase->PMK failed");
goto cleanup;
@@ -914,7 +1020,7 @@ esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl,
}
ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED;
ndl->security_ctx.csid_bitmap = cfg->csid_bitmap;
ndl->security_ctx.csid_bitmap = (uint16_t)(1u << cred->csid);
memcpy(ndl->security_ctx.nd_pmk, pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
memcpy(ndl->security_ctx.nd_pmkid, pair_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN);
@@ -1093,23 +1199,22 @@ int esp_nan_get_ndp_resp_shared_key_desc(uint8_t *buf, size_t buf_len, uint8_t n
if (need_pmk && have_peer_pmkid) {
struct own_svc_info *p_svc = nan_find_own_svc(ndl->publisher_id);
uint8_t matched_pmk[ESP_WIFI_NAN_NDP_PMK_LEN];
if (p_svc && nan_match_pmkid(p_svc, ndl->security_ctx.nd_pmkid, matched_pmk,
ndl->peer_nmi, ndl->peer_ndi)) {
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->security_cfg.csid_bitmap;
memcpy(ndl->security_ctx.nd_pmk, matched_pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
ESP_LOGD(TAG, "NDP Resp Key Desc: resolved PMK from publish (PMKID match)");
forced_memzero(matched_pmk, sizeof(matched_pmk));
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);
} else if (ndl->security_ctx.type != WIFI_NAN_SECURITY_ENCRYPTED) {
forced_memzero(matched_pmk, sizeof(matched_pmk));
NAN_DATA_UNLOCK();
ESP_LOGW(TAG, "NDP Resp Key Desc: NDL not encrypted; send NDP Response without Shared Key Descriptor");
return 0;
} else {
forced_memzero(matched_pmk, sizeof(matched_pmk));
NAN_DATA_UNLOCK();
ESP_LOGW(TAG, "NDP Resp Key Desc: no PMK for peer PMKID; ensure same passphrase on both devices");
ESP_LOGW(TAG, "NDP Resp Key Desc: no PMK for peer PMKID; ensure matching credential on both devices");
return 0;
}
} else if (ndl->security_ctx.type != WIFI_NAN_SECURITY_ENCRYPTED) {
@@ -1264,7 +1369,7 @@ int esp_nan_update_ndp_security_install_mic(uint8_t *m4_body, size_t body_len, u
* Public API: parsers (CSIA / SCIA / Shared Key Descriptor / Publish SDF)
*-----------------------------------------------------------------------*/
void esp_nan_parse_ndp_csia(void *frm, size_t buf_len, wifi_nan_datapath_security_params_t *param)
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;
@@ -1304,7 +1409,7 @@ void esp_nan_parse_ndp_csia(void *frm, size_t buf_len, wifi_nan_datapath_securit
}
}
void esp_nan_parse_ndp_scia(void *frm, size_t buf_len, wifi_nan_datapath_security_params_t *param)
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;
@@ -1522,7 +1627,7 @@ void esp_nan_parse_ndp_key_desc(void *frm, size_t buf_len, uint8_t ndp_id, const
}
esp_err_t esp_nan_parse_publish_security(const uint8_t *attrs, size_t attrs_len,
wifi_nan_discovery_security_params_t *security)
wifi_nan_peer_sdf_security_t *security)
{
uint16_t csia_len = 0;
const uint8_t *csia = NULL;
@@ -1562,7 +1667,7 @@ esp_err_t esp_nan_parse_publish_security(const uint8_t *attrs, size_t attrs_len,
ESP_LOG_BUFFER_HEXDUMP(TAG, scia, scia_len, ESP_LOG_DEBUG);
size_t offset = 0;
while (offset + 4 <= scia_len && security->num_pmkids < ESP_WIFI_NAN_MAX_PMKIDS) {
while (offset + 4 <= scia_len && security->num_pmkids < NAN_PEER_MAX_PMKIDS) {
uint16_t val_len = scia[offset] | (scia[offset + 1] << 8);
uint8_t type = scia[offset + 2];
@@ -1611,15 +1716,17 @@ 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);
uint8_t matched_pmk[ESP_WIFI_NAN_NDP_PMK_LEN];
if (nan_match_pmkid(p_own_svc, s_pending_scia.pmkid, matched_pmk, peer_nmi, peer_ndi)) {
int matched_idx = nan_match_pmkid(p_own_svc, s_pending_scia.pmkid, peer_nmi, peer_ndi);
if (matched_idx >= 0) {
ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED;
memcpy(ndl->security_ctx.nd_pmk, matched_pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
ESP_LOGD(TAG, "NDP Indication: PMKID validated, security=ENCRYPTED");
ndl->security_ctx.csid_bitmap = p_own_svc->derived_security[matched_idx].csid_bitmap;
memcpy(ndl->security_ctx.nd_pmk,
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);
} else {
ESP_LOGW(TAG, "NDP Indication: PMKID validation failed");
}
forced_memzero(matched_pmk, sizeof(matched_pmk));
}
memset(&s_pending_scia, 0, sizeof(s_pending_scia));

View File

@@ -130,19 +130,24 @@ void wifi_nan_publish(void)
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
.security_reqd = 1,
.security_cfg = {
.csid_bitmap = WIFI_NAN_CSID_BIT_NCS_SK_128,
.num_credentials = 1,
.creds = {
{
.csid = WIFI_NAN_CSID_NCS_SK_128,
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
.use_pmk = true,
.use_pmk = true,
#else
.use_pmk = false,
.passphrase = CONFIG_EXAMPLE_NAN_PASSPHRASE,
.use_pmk = false,
.passphrase = CONFIG_EXAMPLE_NAN_PASSPHRASE,
#endif
},
},
},
#endif
};
#if defined(CONFIG_EXAMPLE_NAN_SECURITY_ENABLED) && defined(CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK)
if (!decode_hex_string(CONFIG_EXAMPLE_NAN_PMK, publish_cfg.security_cfg.pmk,
sizeof(publish_cfg.security_cfg.pmk))) {
if (!decode_hex_string(CONFIG_EXAMPLE_NAN_PMK, publish_cfg.security_cfg.creds[0].pmk,
sizeof(publish_cfg.security_cfg.creds[0].pmk))) {
ESP_LOGE(TAG, "Failed to decode CONFIG_EXAMPLE_NAN_PMK");
return;
}

View File

@@ -224,19 +224,24 @@ void wifi_nan_subscribe(void)
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
.security_reqd = 1,
.security_cfg = {
.csid_bitmap = WIFI_NAN_CSID_BIT_NCS_SK_128,
.num_credentials = 1,
.creds = {
{
.csid = WIFI_NAN_CSID_NCS_SK_128,
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
.use_pmk = true,
.use_pmk = true,
#else
.use_pmk = false,
.passphrase = CONFIG_EXAMPLE_NAN_PASSPHRASE,
.use_pmk = false,
.passphrase = CONFIG_EXAMPLE_NAN_PASSPHRASE,
#endif
},
},
},
#endif
};
#if defined(CONFIG_EXAMPLE_NAN_SECURITY_ENABLED) && defined(CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK)
if (!decode_hex_string(CONFIG_EXAMPLE_NAN_PMK, subscribe_cfg.security_cfg.pmk,
sizeof(subscribe_cfg.security_cfg.pmk))) {
if (!decode_hex_string(CONFIG_EXAMPLE_NAN_PMK, subscribe_cfg.security_cfg.creds[0].pmk,
sizeof(subscribe_cfg.security_cfg.creds[0].pmk))) {
ESP_LOGE(TAG, "Failed to decode CONFIG_EXAMPLE_NAN_PMK");
return;
}