diff --git a/components/esp_wifi/include/esp_private/wifi.h b/components/esp_wifi/include/esp_private/wifi.h index d7c5752fc03..e957e40bc7a 100644 --- a/components/esp_wifi/include/esp_private/wifi.h +++ b/components/esp_wifi/include/esp_private/wifi.h @@ -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); diff --git a/components/esp_wifi/include/esp_wifi_types_generic.h b/components/esp_wifi/include/esp_wifi_types_generic.h index 4dc5a26a5d4..a28544220f5 100644 --- a/components/esp_wifi/include/esp_wifi_types_generic.h +++ b/components/esp_wifi/include/esp_wifi_types_generic.h @@ -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; /** diff --git a/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h b/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h index 6b0b143e397..a3ca98bd962 100644 --- a/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h +++ b/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h @@ -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 */ /** diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c index 67ee7db5e58..6e71672fa12 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c @@ -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 diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h b/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h index 94bf3bd7602..c89d0cb75f0 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h @@ -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 diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c index a0f1c4504c0..654b3c19aa2 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c @@ -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 M1–M4 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)); diff --git a/examples/wifi/wifi_aware/nan_publisher/main/publisher_main.c b/examples/wifi/wifi_aware/nan_publisher/main/publisher_main.c index bf87c8b863d..56c6995b5d2 100644 --- a/examples/wifi/wifi_aware/nan_publisher/main/publisher_main.c +++ b/examples/wifi/wifi_aware/nan_publisher/main/publisher_main.c @@ -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; } diff --git a/examples/wifi/wifi_aware/nan_subscriber/main/subscriber_main.c b/examples/wifi/wifi_aware/nan_subscriber/main/subscriber_main.c index e8caea7eb61..c35c4ca042f 100644 --- a/examples/wifi/wifi_aware/nan_subscriber/main/subscriber_main.c +++ b/examples/wifi/wifi_aware/nan_subscriber/main/subscriber_main.c @@ -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; }