Merge branch 'feat/nan_encrypted_data_path' into 'master'

feat(wifi): support encrypted data path in WiFi Aware (NAN)

See merge request espressif/esp-idf!45270
This commit is contained in:
Jiang Jiang Jian
2026-05-20 14:25:17 +08:00
28 changed files with 4233 additions and 182 deletions
+1
View File
@@ -852,5 +852,6 @@ mainmenu "Espressif IoT Development Framework Configuration"
- CONFIG_SPIRAM_SPEED_120M && CONFIG_SPIRAM_MODE_OCT
- CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH
- CONFIG_ESP_WIFI_EAP_TLS1_3
- CONFIG_ESP_WIFI_NAN_SECURITY
- CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS
- CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION
@@ -36,7 +36,7 @@ ic_reset_extra_softap_rx_ba = 0x2f800c78;
ieee80211_align_eb = 0x2f800c7c;
ieee80211_ampdu_reorder = 0x2f800c80;
ieee80211_ampdu_start_age_timer = 0x2f800c84;
ieee80211_encap_esfbuf = 0x2f800c88;
/*ieee80211_encap_esfbuf = 0x2f800c88;*/
ieee80211_is_tx_allowed = 0x2f800c8c;
ieee80211_output_pending_eb = 0x2f800c90;
ieee80211_output_process = 0x2f800c94;
+3
View File
@@ -70,6 +70,9 @@ if(CONFIG_ESP_WIFI_ENABLED OR CONFIG_ESP_HOST_WIFI_ENABLED)
if(CONFIG_ESP_WIFI_NAN_SYNC_ENABLE OR CONFIG_ESP_WIFI_NAN_USD_ENABLE)
list(APPEND srcs "wifi_apps/nan_app/src/nan_app.c")
if(CONFIG_ESP_WIFI_NAN_SECURITY)
list(APPEND srcs "wifi_apps/nan_app/src/nan_security.c")
endif()
endif()
if(CONFIG_ESP_WIFI_ENABLE_ROAMING_APP)
list(APPEND srcs "wifi_apps/roaming_app/src/roaming_app.c")
+17
View File
@@ -593,6 +593,23 @@ menu "Wi-Fi"
help
Enable Wi-Fi Aware: Synchronization feature (NAN-Sync).
config ESP_WIFI_NAN_SECURITY
bool "Enable Wi-Fi Aware: Encrypted Pairwise Datapath (NDP Security)"
depends on ESP_WIFI_NAN_SYNC_ENABLE && IDF_EXPERIMENTAL_FEATURES && ESP_WIFI_MBEDTLS_CRYPTO
select MBEDTLS_PKCS5_C
select MBEDTLS_SHA256_C
default n
help
Enable encrypted pairwise datapath for Wi-Fi Aware (NAN).
Adds PBKDF2 key derivation (mbedTLS PKCS#5), 4-way handshake (M1-M4),
PTK derivation, and CCMP key installation for secured NAN
data links. Disable to save code size when only open
datapaths are needed.
Requires ESP_WIFI_MBEDTLS_CRYPTO + MBEDTLS_PKCS5_C + MBEDTLS_SHA256_C
for ND-PMK derivation via pbkdf2_sha256() (MBEDTLS_MD_C is pulled in
transitively by MBEDTLS_PKCS5_C).
config ESP_WIFI_NAN_USD_ENABLE
bool "Enable Wi-Fi Aware: Unsynchronized service discovery (NAN-USD)"
depends on IDF_EXPERIMENTAL_FEATURES
+305 -6
View File
@@ -34,41 +34,310 @@
extern "C" {
#endif
#define WIFI_MAC_ADDR_LEN 6 /**< Length of an 802.11 MAC address in octets */
#ifndef NAN_IPV6_IDENTIFIER_LEN
#define NAN_IPV6_IDENTIFIER_LEN 8 /**< Length of a NAN IPv6 interface identifier in octets */
#endif
typedef struct {
QueueHandle_t handle; /**< FreeRTOS queue handler */
void *storage; /**< storage for FreeRTOS queue */
} wifi_static_queue_t;
/**
* @brief NAN Datapath Security Type (Wi-Fi Aware v4.0 §9.5.16.1 Table 85, "Security Present" bit)
*/
typedef enum {
WIFI_NAN_SECURITY_OPEN = 0, /**< NDP does not require security */
WIFI_NAN_SECURITY_ENCRYPTED = 1, /**< NDP requires security */
} wifi_nan_security_type_t;
/**
* @brief NAN single-PMKID security parameters
*
* @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; /**< 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_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 {
uint8_t peer_mac[6]; /**< Peer NMI / interface MAC */
uint8_t peer_mac[WIFI_MAC_ADDR_LEN]; /**< Peer NMI / interface MAC */
uint8_t peer_svc_id; /**< Peer's service instance ID */
uint16_t sdea; /**< SDEA control field bits */
uint32_t device_caps; /**< NAN device capabilities */
uint8_t ssi_ver; /**< SSI version (service_match) */
uint8_t *ssi; /**< Service-specific information */
uint16_t ssi_len; /**< SSI length in bytes */
wifi_nan_peer_sdf_security_t *peer_security_params; /**< Peer's discovery security params parsed from SDF */
nan_vendor_ie_t *vendor_ie; /**< Vendor-specific IE, if any */
};
/* NDP Peer info parsed from NAF */
/* NDP Peer info parsed from NAF. */
struct ndp_cb_peer_info {
uint8_t ndp_id;
uint8_t peer_nmi[6];
uint8_t peer_ndi[6];
uint8_t peer_nmi[WIFI_MAC_ADDR_LEN];
uint8_t peer_ndi[WIFI_MAC_ADDR_LEN];
uint8_t *ssi;
uint16_t ssi_len;
};
/* Host-side callbacks the closed-source NAN blob fires up into nan_app.
* Registered once at nan_app init; all callbacks run in blob/WiFi-task
* context, so handlers must be non-blocking and avoid heavy work. */
struct nan_sync_callbacks {
/* Subscriber side: a Publish SDF matching the local subscribe was
* received from a peer.
* sub_id -- local subscribe service instance ID
* peer_info -- publisher details (see struct nan_cb_peer_info) */
void (* service_match)(uint8_t sub_id, struct nan_cb_peer_info *peer_info);
/* Publisher side: a Solicited Publish SDF was just transmitted in
* response to a Subscribe match. Fires once per recipient.
* pub_id -- local publish service instance ID
* peer_info -- subscriber MAC and its service instance ID */
void (* replied)(uint8_t pub_id, struct nan_cb_peer_info *peer_info);
/* Either side: a Follow-up SDF was received for an existing service
* match (post-discovery messaging).
* svc_id -- local service instance ID this Follow-up targets
* peer_info -- sender MAC and SSI payload */
void (* receive)(uint8_t svc_id, struct nan_cb_peer_info *peer_info);
/* Publisher side (NDP Responder): an NDP Request (M1) was received.
* Host either auto-accepts or waits for esp_wifi_nan_datapath_resp()
* depending on the publish-time ndp_resp_needed flag.
* pub_id -- local publish instance bound to this request
* peer_info -- initiator's ndp_id, NMI, NDI, SSI
* device_caps -- initiator's NAN device capabilities */
void (* ndp_indication)(uint8_t pub_id, struct ndp_cb_peer_info *peer_info, uint32_t device_caps);
/* Either side: NDP setup completed (success or rejection).
* status -- NDP_STATUS_ACCEPTED / NDP_STATUS_REJECTED
* peer_info -- peer ndp_id, NMI, NDI
* own_ndi -- locally-assigned NDI for this NDP
* ipv6_identifier -- 8-byte IPv6 interface identifier learnt from
* the peer; all-zero if the peer did not
* advertise one (host then derives it from the
* peer NDI). */
void (* ndp_confirm)(uint8_t status, struct ndp_cb_peer_info *peer_info,
uint8_t own_ndi[6], uint8_t ipv6_identifier[8]);
void (* ndp_terminated)(uint8_t reason, uint8_t ndp_id, uint8_t init_ndi[6]);
uint8_t own_ndi[WIFI_MAC_ADDR_LEN],
uint8_t ipv6_identifier[NAN_IPV6_IDENTIFIER_LEN]);
/* Either side: an established NDP was torn down.
* reason -- termination reason code (peer-initiated, timeout, ...)
* ndp_id -- NDP identifier of the terminated path
* init_ndi -- initiator's NDI for this NDP */
void (* ndp_terminated)(uint8_t reason, uint8_t ndp_id,
uint8_t init_ndi[WIFI_MAC_ADDR_LEN]);
/* TX completion for a host-queued NAN action frame (Follow-up, etc.).
* context -- opaque tag matching the original TX request
* tx_status -- true on transmit success, false on failure */
void (* action_txdone)(uint32_t context, bool tx_status);
/* Initiator-side M2 RX indication. Blob fires this after parsing the
* Responder NDI from the M2 NDP attribute (Wi-Fi Aware v4.0 §9.5.16.1
* Table 82) and before invoking esp_nan_verify_ndp_resp_mic, so the
* host can populate ndl->peer_ndi for spec-correct PTK derivation
* (§7.1.3.5: PTK uses Data Interface addresses). */
void (* ndp_response_indication)(struct ndp_cb_peer_info *peer_info);
};
/* Host helpers for NAN encrypted-datapath, registered via
* esp_nan_internal_register_secure_dp_funcs() at nan_app init.
* Security-gated fields are NULL when CONFIG_ESP_WIFI_NAN_SECURITY=n. */
struct nan_secure_dp_funcs {
/* === Always-present helpers === */
/* Fired by the blob after a host-built NDP setup frame (M2/M4) is
* transmitted, so the host can advance its NDP setup state machine.
* msg_type -- 0x01 = M2 (NDP Response), 0x02 = M4 (Security Install)
* tx_status -- true on TX success, false on failure */
void (*ndp_tx_done_cb)(uint8_t ndp_id, const uint8_t *peer_nmi,
uint8_t msg_type, bool tx_status);
/* === Security-gated helpers (NULL when SECURITY=n) === */
/* --- Length getters: callers use these to size TX buffers before
* invoking the matching construct_*() / get_*_key_desc helper. --- */
/* Byte length of the CSIA emitted for the union of the two cipher
* suite bitmaps (Wi-Fi Aware v4.0 §9.5.21.2). */
uint32_t (*get_csia_len)(uint16_t own_csid_bitmap, uint16_t peer_csid_bitmap);
/* Byte length of a SCIA carrying @c num_pmkids 16-octet ND-PMKIDs
* (§9.5.21.4). */
uint32_t (*get_scia_len)(uint8_t num_pmkids);
/* Byte length of a NAN Shared Key Descriptor attribute (§9.5.21.5)
* with the given Key Data field length. */
uint32_t (*get_shared_key_desc_attr_len)(uint16_t key_data_len);
/* Byte length of the M4 Shared Key Descriptor including any
* encrypted KDE payload (GTK/IGTK/BIGTK). */
int (*ndp_security_install_get_shared_desc_len)(void);
/* --- CSIA / SCIA construction. Each writes a complete NAN
* attribute (header + body) at @c frm and returns bytes
* written, or -1 on error. --- */
/* Build CSIA from the negotiated own/peer cipher suite bitmaps. */
int (*construct_csia)(uint8_t *frm, uint8_t pub_id,
uint16_t own_csid_bitmap, uint16_t peer_csid_bitmap);
/* Build SCIA for a Publish SDF: emits one SCID per provisioned
* ND-PMKID so subscribers can recognise themselves (§9.5.21.4). */
int (*construct_scia_publish)(uint8_t *frm, uint8_t pub_id,
uint8_t num_pmkids,
const uint8_t pmkids[][ESP_WIFI_NAN_NDP_PMKID_LEN]);
/* Build SCIA for an NDP Request (M1): single SCID matching the
* (ndp_id, peer_nmi) credential the initiator selected. */
int (*construct_scia_ndp_req)(uint8_t *frm, uint8_t ndp_id,
const uint8_t *peer_nmi);
/* Build SCIA for an NDP Response (M2): echoes the SCID accepted
* by the responder. */
int (*construct_scia_ndp_resp)(uint8_t *frm, uint8_t ndp_id,
const uint8_t *peer_nmi);
/* --- Shared Key Descriptor builders (M1 / M2 / M3 / M4).
* Each writes the full NAN Shared Key Descriptor attribute
* (§9.5.21.5) into @c buf; the MIC field is left zeroed and
* must be populated by the matching update_*_mic helper after
* the rest of the frame body is in place. Return bytes
* written, -1 on error. --- */
/* M1 (NDP Request): Nonce = INonce, no MIC required. */
int (*get_ndp_req_shared_key_desc)(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* M2 (NDP Response): Nonce = RNonce, MIC over M2 body. */
int (*get_ndp_resp_shared_key_desc)(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* M3 (NDP Confirm): MIC over (Auth_Token || M3 body). */
int (*get_ndp_confirm_shared_key_desc)(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* M4 (Security Install): install bit set; carries any GTK / IGTK /
* BIGTK KDEs encrypted under ND-KEK. */
int (*get_ndp_security_install_key_desc)(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* Compute and cache SHA-256(M1_body)[0:16] in the (ndp_id, peer_nmi)
* NDL slot. The cached Authentication Token is later prepended when
* computing/verifying the M3 MIC (Wi-Fi Aware v4.0 §7.1.3.5). */
int (*capture_m1_auth_token)(const uint8_t *m1_body, size_t body_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* --- MIC compute (TX path). Fill the Key MIC field in
* @c key_desc_attr (which sits inside @c m*_body) using
* ND-KCK derived for this NDP. Return 0 on success. --- */
/* M2 MIC: HMAC over the entire M2 body. */
int (*update_ndp_resp_mic)(uint8_t *m2_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* M3 MIC: HMAC over (cached M1 Auth_Token || M3 body). */
int (*update_ndp_confirm_mic)(uint8_t *m3_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* M4 MIC: HMAC over the entire M4 body. */
int (*update_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);
/* --- MIC verify (RX path). Recompute the expected Key MIC over
* the same input as the matching update_* helper and compare
* against the value in @c key_desc_attr. Return 0 on pass,
* -1 on mismatch -- caller tears down the NDP on -1. --- */
/* M2 MIC verify (initiator). */
int (*verify_ndp_resp_mic)(uint8_t *m2_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* M3 MIC verify (responder; uses cached M1 Auth_Token). */
int (*verify_ndp_confirm_mic)(uint8_t *m3_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
/* M4 MIC verify (initiator). */
int (*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);
/* --- RX-path attribute parsers. --- */
/* Parse CSIA from a received NDP setup frame and populate the
* negotiated cipher-suite bitmap in @c param. */
void (*parse_ndp_csia)(void *frm, size_t buf_len, wifi_nan_security_params_t *param);
/* Parse SCIA from a received NDP setup frame and populate the
* selected ND-PMKID + Publish ID in @c param. */
void (*parse_ndp_scia)(void *frm, size_t buf_len, wifi_nan_security_params_t *param);
/* Parse the NAN Shared Key Descriptor attribute from a received
* M2/M3/M4 frame: advances the NDL replay counter, captures the
* peer Nonce, decrypts KDE payloads, and installs GTK/IGTK/BIGTK
* as applicable. */
void (*parse_ndp_key_desc)(void *frm, size_t buf_len, uint8_t ndp_id, const uint8_t *peer_nmi);
/* Parse security-related attributes from an inbound Publish SDF
* (CSIA + SCIA list) and populate @c security so the subscriber
* state machine can pick a compatible credential. */
esp_err_t (*parse_publish_security)(const uint8_t *attrs, size_t attrs_len,
wifi_nan_peer_sdf_security_t *security);
/* 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);
/* Returns the cipher-suite bitmap negotiated for an in-flight NDP,
* or 0 if the NDP is open. Used by RX/TX paths to decide whether
* to apply CCMP/GCMP and which key length to use. */
uint16_t (*get_ndp_security_csid)(uint8_t ndp_id, const uint8_t *peer_nmi);
};
/**
@@ -810,6 +1079,36 @@ esp_err_t esp_nan_internal_datapath_end(wifi_nan_datapath_end_req_t *req);
*/
esp_err_t esp_nan_internal_register_callbacks(struct nan_sync_callbacks *cb);
/**
* @brief Register the NAN secure-datapath helper table with the WiFi libraries.
*
* Pass a pointer to a static struct populated by the host; pass NULL to
* deregister at deinit time.
*
* @param fns Pointer to populated nan_secure_dp_funcs (or NULL to deregister)
* @return ESP_OK on success
*/
esp_err_t esp_nan_internal_register_secure_dp_funcs(struct nan_secure_dp_funcs *fns);
/**
* @brief Install NAN pairwise/group key into Wi-Fi firmware key table
*
* @param[in] alg Cipher algorithm identifier (for CCMP use 3)
* @param[in] addr Peer address used for key lookup (NDI for NAN data path)
* @param[in] key_idx Key index (use 0 for pairwise key)
* @param[in] set_tx Set key as TX key when non-zero
* @param[in] seq Initial sequence/RSC value (typically 8 bytes)
* @param[in] seq_len Length of seq in bytes
* @param[in] key Key material
* @param[in] key_len Key length in bytes
* @param[in] key_flag Key usage flags bitmask
*
* @return 0 on success, negative value on failure
*/
int esp_wifi_set_nan_key_internal(int alg, uint8_t *addr, int key_idx, int set_tx,
uint8_t *seq, size_t seq_len, uint8_t *key, size_t key_len, int key_flag);
/**
* @brief Connect WiFi station to the AP.
*
@@ -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
@@ -864,6 +866,10 @@ typedef struct {
#define ESP_WIFI_NDP_ROLE_INITIATOR 1 /**< Initiator role for NAN Data Path */
#define ESP_WIFI_NDP_ROLE_RESPONDER 2 /**< Responder role for NAN Data Path */
#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_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 */
#define ESP_WIFI_MAX_SVC_INFO_LEN 64 /**< Maximum length of NAN service info */
@@ -905,6 +911,64 @@ typedef enum {
NAN_SUBSCRIBE_PASSIVE, /**< Passively listens to Publish frames */
} wifi_nan_service_type_t;
/**
* @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
* them via csid_bitmap will cause esp_wifi_nan_publish_service() and
* esp_wifi_nan_subscribe_service() to fail.
*/
typedef enum {
WIFI_NAN_CSID_NCS_SK_128 = 1, /**< NCS-SK-128 (PSK/Passphrase) */
WIFI_NAN_CSID_NCS_SK_256 = 2, /**< NCS-SK-256 (PSK/Passphrase). Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_128 = 3, /**< NCS-PK-2WDH-128. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_256 = 4, /**< NCS-PK-2WDH-256. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_PASN_128 = 7, /**< NCS-PK-PASN-128. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_PASN_256 = 8, /**< NCS-PK-PASN-256. Reserved: not supported right now. */
} wifi_nan_cipher_suite_id_t;
#define WIFI_NAN_CSID_BIT_NCS_SK_128 (1 << WIFI_NAN_CSID_NCS_SK_128)
#define WIFI_NAN_CSID_BIT_NCS_SK_256 (1 << WIFI_NAN_CSID_NCS_SK_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_128 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_256 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_128 (1 << WIFI_NAN_CSID_NCS_PK_PASN_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256)
/**
* @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 {
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;
/**
* @brief USD specific configuration parameters
*
@@ -924,9 +988,9 @@ typedef struct {
* @brief NAN Vendor Specific Attribute format
*/
typedef struct {
uint8_t vendor_oui[3]; /**< Vendor identifier (OUI) */
uint16_t body_len; /**< Length of body payload (max NAN_VENDOR_IE_MAX_BODY_LEN bytes) */
uint8_t *body; /**< Vendor specific body payload */
uint8_t vendor_oui[WIFI_OUI_LEN]; /**< Vendor identifier (OUI) */
uint16_t body_len; /**< Length of body payload (max 255 bytes) */
uint8_t *body; /**< Vendor specific body payload */
} nan_vendor_ie_t;
/**
@@ -943,12 +1007,16 @@ typedef struct {
uint8_t fsd_gas: 1; /**< 0 - Follow-up used for FSD, 1 - GAS used for FSD */
uint8_t ndp_resp_needed: 1; /**< 0 - Auto-Accept NDP Requests, 1 - Require explicit response with esp_wifi_nan_datapath_resp */
uint8_t usd_discovery_flag: 1; /**< 0 - NAN Synchronization for Discovery, 1 - USD for Discovery. 'NAN Discovery flag' from specification */
uint8_t reserved: 2; /**< Reserved */
uint8_t security_reqd: 1; /**< Security: 0 - Open, 1 - Required (NDP Security) */
uint8_t reserved: 1; /**< Reserved */
uint16_t ssi_len; /**< Length of service specific info, maximum allowed length - ESP_WIFI_MAX_SVC_SSI_LEN */
uint8_t *ssi; /**< Service Specific Info of type wifi_nan_wfa_ssi_t for WFA defined protocols, otherwise proprietary and defined by Applications */
unsigned int ttl; /**< Run publish function for a given time interval in seconds. If ttl=0 and usd_discovery_flag is enabled,
only one Publish message is transmitted */
wifi_nan_usd_config_t usd_publish_config; /**< USD configuration parameters. Relevant only when 'usd_discovery_flag' is set. */
wifi_nan_discovery_security_params_t *security_cfg; /**< Security configuration parameters. Used when security_reqd is set, NULL otherwise.
The driver makes a private copy during esp_wifi_nan_publish_service();
the caller may free this immediately after the call returns. */
nan_vendor_ie_t *vendor_ie; /**< Vendor specific IE to be added in publish frames */
} wifi_nan_publish_cfg_t;
@@ -965,12 +1033,16 @@ typedef struct {
uint8_t fsd_reqd: 1; /**< Further Service Discovery(FSD) required */
uint8_t fsd_gas: 1; /**< 0 - Follow-up used for FSD, 1 - GAS used for FSD */
uint8_t usd_discovery_flag: 1; /**< 0 - NAN Synchronization for Discovery, 1 - USD for Discovery. 'NAN Discovery flag' from specification */
uint8_t reserved: 3; /**< Reserved */
uint8_t security_reqd: 1; /**< Security: 0 - Open, 1 - Required (NDP Security) */
uint8_t reserved: 2; /**< Reserved */
uint16_t ssi_len; /**< Length of service specific info, maximum allowed length - ESP_WIFI_MAX_SVC_SSI_LEN */
uint8_t *ssi; /**< Service Specific Info of type wifi_nan_wfa_ssi_t for WFA defined protocols, otherwise proprietary and defined by Applications */
unsigned int ttl; /**< Run subscribe function for a given time interval in seconds. If ttl=0 and usd_discovery_flag is enabled,
the subscriber listens until the first service match is reported. */
wifi_nan_usd_config_t usd_subscribe_config; /**< USD configuration parameters. Relevant only when 'usd_discovery_flag' is set. */
wifi_nan_discovery_security_params_t *security_cfg; /**< Security configuration parameters. Used when security_reqd is set, NULL otherwise.
The driver makes a private copy during esp_wifi_nan_subscribe_service();
the caller may free this immediately after the call returns. */
nan_vendor_ie_t *vendor_ie; /**< Vendor specific IE to be added in subscribe frames */
} wifi_nan_subscribe_cfg_t;
@@ -990,16 +1062,41 @@ typedef struct {
/**
* @brief NAN Datapath Request parameters
*
* @note Datapath security is governed by the security_cfg passed to
* esp_wifi_nan_subscribe_service(); the NAN library derives ND-PMK,
* ND-PMKID and cipher selection internally from that subscribe-time
* configuration and applies them to every NDP initiated against the
* matched publisher. Per-NDP security parameters are not exposed on
* this struct: the caller never handles raw key material.
*
* @note NCS-SK only. For pairing-based cipher suites (NCS-PK-PASN), the
* per-peer ND-PMK is derived from a cached NPKSA and will be
* installed via a separate pairing API; this struct will remain
* unchanged.
*/
typedef struct {
uint8_t pub_id; /**< Publisher's service instance id */
uint8_t peer_mac[6]; /**< Peer's MAC address */
bool confirm_required; /**< NDP Confirm frame required */
bool confirm_required; /**< NDP Confirm frame required. Always used for the secure NDP handshake. */
} wifi_nan_datapath_req_t;
/**
* @brief NAN Datapath Response parameters
*
* @note Datapath security is governed by the security_cfg passed to
* esp_wifi_nan_publish_service(); the NAN library derives ND-PMK,
* ND-PMKID and cipher selection internally from that publish-time
* configuration and applies them to every NDP this responder
* accepts. Per-NDP security parameters are not exposed on this
* struct: the caller never handles raw key material.
*
* @note NCS-SK only. For pairing-based cipher suites (NCS-PK-PASN), the
* per-peer ND-PMK is derived from a cached NPKSA and will be
* installed via a separate pairing API; this struct will remain
* unchanged. The responder must already have the PMK cached at
* M1 receive time (PMKID lookup happens before the indication
* event), so per-NDP credential injection at response time is
* not viable.
*/
typedef struct {
bool accept; /**< True - Accept incoming NDP, False - Reject it */
@@ -1230,8 +1327,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 */
/**
@@ -1412,7 +1507,8 @@ typedef struct {
uint8_t fsd_reqd: 1; /**< Further Service Discovery(FSD) required */
uint8_t fsd_gas: 1; /**< 0 - Follow-up used for FSD, 1 - GAS used for FSD */
uint8_t ndpe_support: 1; /**< NDPE supported by peer */
uint8_t reserved: 4; /**< Reserved */
uint8_t security_reqd: 1; /**< Security: 0 - Open, 1 - Required (NDP Security) */
uint8_t reserved: 3; /**< Reserved */
uint32_t reserved_1; /**< Reserved */
uint32_t reserved_2; /**< Reserved */
uint8_t ssi_version; /**< Indicates version of SSI in Publish instance, 0 if not available */
@@ -572,6 +572,23 @@ config WIFI_RMT_NAN_SYNC_ENABLE
help
Enable Wi-Fi Aware: Synchronization feature (NAN-Sync).
config WIFI_RMT_NAN_SECURITY
bool "Enable Wi-Fi Aware: Encrypted Pairwise Datapath (NDP Security)"
depends on WIFI_RMT_NAN_SYNC_ENABLE && IDF_EXPERIMENTAL_FEATURES && WIFI_RMT_MBEDTLS_CRYPTO
select MBEDTLS_PKCS5_C
select MBEDTLS_SHA256_C
default n
help
Enable encrypted pairwise datapath for Wi-Fi Aware (NAN).
Adds PBKDF2 key derivation (mbedTLS PKCS#5), 4-way handshake (M1-M4),
PTK derivation, and CCMP key installation for secured NAN
data links. Disable to save code size when only open
datapaths are needed.
Requires WIFI_RMT_MBEDTLS_CRYPTO + MBEDTLS_PKCS5_C + MBEDTLS_SHA256_C
for ND-PMK derivation via pbkdf2_sha256() (MBEDTLS_MD_C is pulled in
transitively by MBEDTLS_PKCS5_C).
config WIFI_RMT_NAN_USD_ENABLE
bool "Enable Wi-Fi Aware: Unsynchronized service discovery (NAN-USD)"
depends on IDF_EXPERIMENTAL_FEATURES
@@ -290,6 +290,13 @@ if WIFI_RMT_NAN_SYNC_ENABLE
default WIFI_RMT_NAN_SYNC_ENABLE
endif
if WIFI_RMT_NAN_SECURITY
config ESP_WIFI_NAN_SECURITY # ignore: multiple-definition
bool
depends on WIFI_RMT_NAN_SYNC_ENABLE && IDF_EXPERIMENTAL_FEATURES && WIFI_RMT_MBEDTLS_CRYPTO
default WIFI_RMT_NAN_SECURITY
endif
if WIFI_RMT_NAN_USD_ENABLE
config ESP_WIFI_NAN_USD_ENABLE # ignore: multiple-definition
bool
@@ -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
@@ -864,6 +866,10 @@ typedef struct {
#define ESP_WIFI_NDP_ROLE_INITIATOR 1 /**< Initiator role for NAN Data Path */
#define ESP_WIFI_NDP_ROLE_RESPONDER 2 /**< Responder role for NAN Data Path */
#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_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 */
#define ESP_WIFI_MAX_SVC_INFO_LEN 64 /**< Maximum length of NAN service info */
@@ -905,6 +911,64 @@ typedef enum {
NAN_SUBSCRIBE_PASSIVE, /**< Passively listens to Publish frames */
} wifi_nan_service_type_t;
/**
* @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
* them via csid_bitmap will cause esp_wifi_nan_publish_service() and
* esp_wifi_nan_subscribe_service() to fail.
*/
typedef enum {
WIFI_NAN_CSID_NCS_SK_128 = 1, /**< NCS-SK-128 (PSK/Passphrase) */
WIFI_NAN_CSID_NCS_SK_256 = 2, /**< NCS-SK-256 (PSK/Passphrase). Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_128 = 3, /**< NCS-PK-2WDH-128. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_256 = 4, /**< NCS-PK-2WDH-256. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_PASN_128 = 7, /**< NCS-PK-PASN-128. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_PASN_256 = 8, /**< NCS-PK-PASN-256. Reserved: not supported right now. */
} wifi_nan_cipher_suite_id_t;
#define WIFI_NAN_CSID_BIT_NCS_SK_128 (1 << WIFI_NAN_CSID_NCS_SK_128)
#define WIFI_NAN_CSID_BIT_NCS_SK_256 (1 << WIFI_NAN_CSID_NCS_SK_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_128 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_256 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_128 (1 << WIFI_NAN_CSID_NCS_PK_PASN_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256)
/**
* @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 {
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;
/**
* @brief USD specific configuration parameters
*
@@ -924,9 +988,9 @@ typedef struct {
* @brief NAN Vendor Specific Attribute format
*/
typedef struct {
uint8_t vendor_oui[3]; /**< Vendor identifier (OUI) */
uint16_t body_len; /**< Length of body payload (max NAN_VENDOR_IE_MAX_BODY_LEN bytes) */
uint8_t *body; /**< Vendor specific body payload */
uint8_t vendor_oui[WIFI_OUI_LEN]; /**< Vendor identifier (OUI) */
uint16_t body_len; /**< Length of body payload (max 255 bytes) */
uint8_t *body; /**< Vendor specific body payload */
} nan_vendor_ie_t;
/**
@@ -943,12 +1007,16 @@ typedef struct {
uint8_t fsd_gas: 1; /**< 0 - Follow-up used for FSD, 1 - GAS used for FSD */
uint8_t ndp_resp_needed: 1; /**< 0 - Auto-Accept NDP Requests, 1 - Require explicit response with esp_wifi_nan_datapath_resp */
uint8_t usd_discovery_flag: 1; /**< 0 - NAN Synchronization for Discovery, 1 - USD for Discovery. 'NAN Discovery flag' from specification */
uint8_t reserved: 2; /**< Reserved */
uint8_t security_reqd: 1; /**< Security: 0 - Open, 1 - Required (NDP Security) */
uint8_t reserved: 1; /**< Reserved */
uint16_t ssi_len; /**< Length of service specific info, maximum allowed length - ESP_WIFI_MAX_SVC_SSI_LEN */
uint8_t *ssi; /**< Service Specific Info of type wifi_nan_wfa_ssi_t for WFA defined protocols, otherwise proprietary and defined by Applications */
unsigned int ttl; /**< Run publish function for a given time interval in seconds. If ttl=0 and usd_discovery_flag is enabled,
only one Publish message is transmitted */
wifi_nan_usd_config_t usd_publish_config; /**< USD configuration parameters. Relevant only when 'usd_discovery_flag' is set. */
wifi_nan_discovery_security_params_t *security_cfg; /**< Security configuration parameters. Used when security_reqd is set, NULL otherwise.
The driver makes a private copy during esp_wifi_nan_publish_service();
the caller may free this immediately after the call returns. */
nan_vendor_ie_t *vendor_ie; /**< Vendor specific IE to be added in publish frames */
} wifi_nan_publish_cfg_t;
@@ -965,12 +1033,16 @@ typedef struct {
uint8_t fsd_reqd: 1; /**< Further Service Discovery(FSD) required */
uint8_t fsd_gas: 1; /**< 0 - Follow-up used for FSD, 1 - GAS used for FSD */
uint8_t usd_discovery_flag: 1; /**< 0 - NAN Synchronization for Discovery, 1 - USD for Discovery. 'NAN Discovery flag' from specification */
uint8_t reserved: 3; /**< Reserved */
uint8_t security_reqd: 1; /**< Security: 0 - Open, 1 - Required (NDP Security) */
uint8_t reserved: 2; /**< Reserved */
uint16_t ssi_len; /**< Length of service specific info, maximum allowed length - ESP_WIFI_MAX_SVC_SSI_LEN */
uint8_t *ssi; /**< Service Specific Info of type wifi_nan_wfa_ssi_t for WFA defined protocols, otherwise proprietary and defined by Applications */
unsigned int ttl; /**< Run subscribe function for a given time interval in seconds. If ttl=0 and usd_discovery_flag is enabled,
the subscriber listens until the first service match is reported. */
wifi_nan_usd_config_t usd_subscribe_config; /**< USD configuration parameters. Relevant only when 'usd_discovery_flag' is set. */
wifi_nan_discovery_security_params_t *security_cfg; /**< Security configuration parameters. Used when security_reqd is set, NULL otherwise.
The driver makes a private copy during esp_wifi_nan_subscribe_service();
the caller may free this immediately after the call returns. */
nan_vendor_ie_t *vendor_ie; /**< Vendor specific IE to be added in subscribe frames */
} wifi_nan_subscribe_cfg_t;
@@ -990,16 +1062,41 @@ typedef struct {
/**
* @brief NAN Datapath Request parameters
*
* @note Datapath security is governed by the security_cfg passed to
* esp_wifi_nan_subscribe_service(); the NAN library derives ND-PMK,
* ND-PMKID and cipher selection internally from that subscribe-time
* configuration and applies them to every NDP initiated against the
* matched publisher. Per-NDP security parameters are not exposed on
* this struct: the caller never handles raw key material.
*
* @note NCS-SK only. For pairing-based cipher suites (NCS-PK-PASN), the
* per-peer ND-PMK is derived from a cached NPKSA and will be
* installed via a separate pairing API; this struct will remain
* unchanged.
*/
typedef struct {
uint8_t pub_id; /**< Publisher's service instance id */
uint8_t peer_mac[6]; /**< Peer's MAC address */
bool confirm_required; /**< NDP Confirm frame required */
bool confirm_required; /**< NDP Confirm frame required. Always used for the secure NDP handshake. */
} wifi_nan_datapath_req_t;
/**
* @brief NAN Datapath Response parameters
*
* @note Datapath security is governed by the security_cfg passed to
* esp_wifi_nan_publish_service(); the NAN library derives ND-PMK,
* ND-PMKID and cipher selection internally from that publish-time
* configuration and applies them to every NDP this responder
* accepts. Per-NDP security parameters are not exposed on this
* struct: the caller never handles raw key material.
*
* @note NCS-SK only. For pairing-based cipher suites (NCS-PK-PASN), the
* per-peer ND-PMK is derived from a cached NPKSA and will be
* installed via a separate pairing API; this struct will remain
* unchanged. The responder must already have the PMK cached at
* M1 receive time (PMKID lookup happens before the indication
* event), so per-NDP credential injection at response time is
* not viable.
*/
typedef struct {
bool accept; /**< True - Accept incoming NDP, False - Reject it */
@@ -1230,8 +1327,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 */
/**
@@ -1412,7 +1507,8 @@ typedef struct {
uint8_t fsd_reqd: 1; /**< Further Service Discovery(FSD) required */
uint8_t fsd_gas: 1; /**< 0 - Follow-up used for FSD, 1 - GAS used for FSD */
uint8_t ndpe_support: 1; /**< NDPE supported by peer */
uint8_t reserved: 4; /**< Reserved */
uint8_t security_reqd: 1; /**< Security: 0 - Open, 1 - Required (NDP Security) */
uint8_t reserved: 3; /**< Reserved */
uint32_t reserved_1; /**< Reserved */
uint32_t reserved_2; /**< Reserved */
uint8_t ssi_version; /**< Indicates version of SSI in Publish instance, 0 if not available */
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
project(nan_crypto_test)
@@ -0,0 +1,32 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | --------- |
# NAN Crypto Test Application
This test application validates the NAN ND-PMK derivation implementation against
official Wi-Fi Aware Specification v4.0 test vectors (Appendix M.1).
## Building
```bash
cd components/esp_wifi/test_apps/nan_crypto_test
idf.py build
```
## Running
```bash
idf.py -p PORT flash monitor
```
## Test Vectors
The application tests three official test vectors:
1. Passphrase: "NAN"
2. Passphrase: "NAN2"
3. Passphrase: "NAN-Testvector-Phrase"
All use:
- Cipher ID: 0x01 (NCS-SK-128)
- Service ID: 2b9c450f6671
- NMI: 02904c12d001
@@ -0,0 +1,6 @@
idf_component_register(SRCS "nan_pmk_test.c"
"test_main.c"
INCLUDE_DIRS "."
REQUIRES unity esp_wifi nvs_flash esp_netif esp_event
PRIV_REQUIRES wpa_supplicant
PRIV_INCLUDE_DIRS ../../../../wpa_supplicant/src/)
@@ -0,0 +1,4 @@
#
# Automatically generated file; DO NOT EDIT.
# NAN Crypto Test Configuration
#
@@ -0,0 +1,175 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* NAN ND-PMK Derivation Test Vectors
*
* Test vectors from Wi-Fi Aware Specification v4.0
* Appendix M.1 - Example test vectors for pass-phrase to ND-PMK conversion
*/
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_wifi_types.h"
#include "utils/common.h" /* u8 typedef used by sha256.h */
#include "crypto/sha256.h" /* mbedTLS-backed pbkdf2_sha256 from wpa_supplicant */
static const char *TAG = "nan_pmk_test";
typedef struct {
const char *passphrase;
uint8_t cipher_id;
uint8_t service_id[6];
uint8_t nmi[6];
uint8_t expected_pmk[32];
bool use_pmk;
uint8_t input_pmk[32];
const char *description;
} test_vector_t;
/* Test Vector 1 */
static const test_vector_t test_vector_1 = {
.passphrase = "NAN",
.cipher_id = 0x01,
.service_id = {0x2b, 0x9c, 0x45, 0x0f, 0x66, 0x71},
.nmi = {0x02, 0x90, 0x4c, 0x12, 0xd0, 0x01},
.expected_pmk = {
0xee, 0x35, 0x85, 0x06, 0x30, 0x56, 0xd1, 0x64,
0xd1, 0x54, 0x54, 0xad, 0x39, 0x01, 0x0d, 0x4e,
0x26, 0x40, 0xb0, 0xd8, 0x2f, 0xb2, 0x4a, 0x2d,
0x68, 0x99, 0x86, 0x2d, 0x27, 0x3c, 0x68, 0xbf
},
.description = "Test Vector 1: Passphrase 'NAN'"
};
/* Test Vector 2 */
static const test_vector_t test_vector_2 = {
.passphrase = "NAN2",
.cipher_id = 0x01,
.service_id = {0x2b, 0x9c, 0x45, 0x0f, 0x66, 0x71},
.nmi = {0x02, 0x90, 0x4c, 0x12, 0xd0, 0x01},
.expected_pmk = {
0x87, 0x53, 0x4f, 0xa7, 0x74, 0xb1, 0x73, 0x2d,
0xb0, 0x42, 0x66, 0xc4, 0x2c, 0x5d, 0x08, 0xd0,
0x9e, 0x58, 0x63, 0xd1, 0xda, 0x11, 0xce, 0x25,
0x76, 0xa8, 0xf1, 0x55, 0xfe, 0x26, 0xcd, 0x2a
},
.description = "Test Vector 2: Passphrase 'NAN2'"
};
/* Test Vector 3 */
static const test_vector_t test_vector_3 = {
.passphrase = "NAN-Testvector-Phrase",
.cipher_id = 0x01,
.service_id = {0x2b, 0x9c, 0x45, 0x0f, 0x66, 0x71},
.nmi = {0x02, 0x90, 0x4c, 0x12, 0xd0, 0x01},
.expected_pmk = {
0x4d, 0xc8, 0x6c, 0xcd, 0xa8, 0x04, 0xf4, 0xe2,
0xe1, 0x39, 0xfc, 0xa5, 0xdd, 0xd2, 0x1b, 0xa5,
0xc0, 0xb1, 0xb6, 0xed, 0x31, 0xff, 0xd7, 0x00,
0x5e, 0x2d, 0x56, 0xf1, 0xe7, 0xbf, 0x51, 0x87
},
.description = "Test Vector 3: Passphrase 'NAN-Testvector-Phrase'"
};
/* Test Vector 4: Direct PMK */
static const test_vector_t test_vector_4 = {
.use_pmk = true,
.input_pmk = {
0xee, 0x35, 0x85, 0x06, 0x30, 0x56, 0xd1, 0x64,
0xd1, 0x54, 0x54, 0xad, 0x39, 0x01, 0x0d, 0x4e,
0x26, 0x40, 0xb0, 0xd8, 0x2f, 0xb2, 0x4a, 0x2d,
0x68, 0x99, 0x86, 0x2d, 0x27, 0x3c, 0x68, 0xbf
},
.expected_pmk = {
0xee, 0x35, 0x85, 0x06, 0x30, 0x56, 0xd1, 0x64,
0xd1, 0x54, 0x54, 0xad, 0x39, 0x01, 0x0d, 0x4e,
0x26, 0x40, 0xb0, 0xd8, 0x2f, 0xb2, 0x4a, 0x2d,
0x68, 0x99, 0x86, 0x2d, 0x27, 0x3c, 0x68, 0xbf
},
.description = "Test Vector 4: Direct PMK (using PMK from Vector 1)"
};
static void print_hex(const char *label, const uint8_t *data, size_t len)
{
printf("%s: ", label);
for (size_t i = 0; i < len; i++) {
printf("%02x", data[i]);
}
printf("\n");
}
static bool run_test_vector(const test_vector_t *tv)
{
uint8_t salt[14];
uint8_t pmk[32];
ESP_LOGI(TAG, "\n=== %s ===", tv->description);
if (tv->use_pmk) {
ESP_LOGI(TAG, "(Direct PMK Mode)");
memcpy(pmk, tv->input_pmk, 32);
} else {
/* Construct Salt = Salt_Version || Cipher_Suite_ID || Service_ID || Publisher_NMI */
salt[0] = 0x00; /* Salt Version */
salt[1] = tv->cipher_id;
memcpy(&salt[2], tv->service_id, 6);
memcpy(&salt[8], tv->nmi, 6);
print_hex("Passphrase", (const uint8_t *)tv->passphrase, strlen(tv->passphrase));
print_hex("Salt", salt, sizeof(salt));
/* Derive PMK using PBKDF2-SHA256 */
if (pbkdf2_sha256(tv->passphrase, salt, sizeof(salt), 4096, pmk, 32) != 0) {
ESP_LOGE(TAG, "PBKDF2-SHA256 derivation failed!");
return false;
}
}
print_hex("Derived PMK ", pmk, 32);
print_hex("Expected PMK ", tv->expected_pmk, 32);
/* Compare with expected PMK */
if (memcmp(pmk, tv->expected_pmk, 32) == 0) {
ESP_LOGI(TAG, "✓ TEST PASSED");
return true;
} else {
ESP_LOGE(TAG, "✗ TEST FAILED - PMK mismatch!");
return false;
}
}
void nan_pmk_run_test_vectors(void)
{
int passed = 0;
int total = 4;
ESP_LOGI(TAG, "\n");
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, " NAN ND-PMK Derivation Test Vectors");
ESP_LOGI(TAG, " Wi-Fi Aware Spec v4.0 - Appendix M.1");
ESP_LOGI(TAG, "========================================");
if (run_test_vector(&test_vector_1)) {
passed++;
}
if (run_test_vector(&test_vector_2)) {
passed++;
}
if (run_test_vector(&test_vector_3)) {
passed++;
}
if (run_test_vector(&test_vector_4)) {
passed++;
}
ESP_LOGI(TAG, "\n");
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, " Test Results: %d/%d PASSED", passed, total);
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, "\n");
}
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* NAN ND-PMK Test Vector Header
*/
#ifndef NAN_PMK_TEST_H
#define NAN_PMK_TEST_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Run NAN ND-PMK derivation test vectors
*
* Tests the PBKDF2 derivation against official Wi-Fi Aware Specification
* test vectors (Appendix M.1)
*/
void nan_pmk_run_test_vectors(void);
#ifdef __cplusplus
}
#endif
#endif /* NAN_PMK_TEST_H */
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* NAN Crypto Test Main
*
* Test application for NAN ND-PMK derivation
*/
#include <stdio.h>
#include "unity.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "nan_pmk_test.h"
static const char *TAG = "nan_test";
void app_main(void)
{
/* Initialize NVS */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/* Initialize Wi-Fi (needed for crypto funcs) */
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, " NAN ND-PMK Derivation Test");
ESP_LOGI(TAG, "========================================\n");
/* Run test vectors */
nan_pmk_run_test_vectors();
ESP_LOGI(TAG, "\n========================================");
ESP_LOGI(TAG, " All tests completed");
ESP_LOGI(TAG, "========================================");
}
@@ -0,0 +1 @@
CONFIG_ESP_WIFI_NAN_SYNC_ENABLE=y
@@ -30,10 +30,12 @@ extern "C" {
#define NAN_MAX_PEERS_RECORD 15
#define ESP_NAN_PUBLISH 2
#define ESP_NAN_SUBSCRIBE 1
#define NAN_IPV6_ADDR_ID_LEN 8
#ifndef NAN_IPV6_IDENTIFIER_LEN
#define NAN_IPV6_IDENTIFIER_LEN 8
#endif
#define IS_ZERO_NAN_ADDR_ID(a) (!((a)[0] | (a)[1] | (a)[2] | (a)[3] | \
(a)[4] | (a)[5] | (a)[6] | (a)[7]))
#define IS_ZERO_NAN_IPV6_IDENTIFIER(a) (!((a)[0] | (a)[1] | (a)[2] | (a)[3] | \
(a)[4] | (a)[5] | (a)[6] | (a)[7]))
#define ESP_NAN_SET_IPV6_LINKLOCAL_FROM_IDENTIFIER(_target_addr, _identifier) \
do { \
@@ -42,7 +44,7 @@ extern "C" {
(_target_addr).u_addr.ip6.addr[1] = 0; \
memcpy(&(_target_addr).u_addr.ip6.addr[2], \
(_identifier), \
NAN_IPV6_ADDR_ID_LEN); \
NAN_IPV6_IDENTIFIER_LEN); \
} while (0)
/** Parameters of a peer service record */
@@ -85,7 +87,7 @@ esp_err_t esp_wifi_nan_sync_stop(void);
*
* @attention This API should be called by the Subscriber after a match occurs with a Publisher.
*
* @param req NAN Datapath Request parameters.
* @param req NAN Datapath Request parameters
*
* @return
* - non-zero NAN Datapath identifier: If NAN datapath req was accepted by publisher
@@ -99,7 +101,7 @@ uint8_t esp_wifi_nan_datapath_req(wifi_nan_datapath_req_t *req);
* @attention This API should be called if ndp_resp_needed is set 1 in wifi_nan_publish_cfg_t and
* a WIFI_EVENT_NDP_INDICATION event is received due to an incoming NDP request.
*
* @param resp NAN Datapath Response parameters.
* @param resp NAN Datapath Response parameters
*
* @return
* - ESP_OK: succeed
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,372 @@
/*
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* Internal declarations shared between nan_app.c and nan_security.c.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/queue.h>
#include "esp_err.h"
#include "esp_wifi_types_generic.h"
#include "esp_private/wifi.h"
#include "esp_nan.h"
#include "os.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Macros */
#ifndef MACADDR_LEN
#define MACADDR_LEN 6
#endif
#define MACADDR_EQUAL(a1, a2) (memcmp(a1, a2, MACADDR_LEN) == 0)
#define MACADDR_COPY(dst, src) (memcpy(dst, src, MACADDR_LEN))
/*
* Shared lock used by both files.
*
* NAN_DATA_LOCK contract
* ----------------------
* s_nan_data_lock guards s_nan_ctx (NDL[], own_svc[], state, event,
* netif). Anything that reads or mutates these fields takes the lock.
*
* !!! MUST NOT be held across any esp_nan_internal_* call !!!
*
* The blob-side esp_nan_internal_* entry points (datapath_req,
* datapath_resp, datapath_end, publish_service, subscribe_service,
* send_followup, register_callbacks) re-enter host code from the WiFi
* task to:
* - assemble M1 / M2 / M3 / M4 NDP frames (esp_nan_get_ndp_*_key_desc,
* esp_nan_construct_csia / scia, esp_nan_capture_m1_auth_token,
* esp_nan_update_ndp_*_mic, esp_nan_verify_ndp_*_mic)
* - parse inbound NDP frames (esp_nan_parse_ndp_*)
* - fire indication / confirm / response_indication / terminated
* callbacks (nan_app_ndp_*_cb)
*
* All of those re-entry points take NAN_DATA_LOCK themselves. Holding
* the lock around the blob call deadlocks the WiFi task as soon as it
* tries to call back. Pattern:
*
* NAN_DATA_LOCK();
* ... mutate / capture state needed by the call ...
* NAN_DATA_UNLOCK();
* err = esp_nan_internal_datapath_req(...);
* NAN_DATA_LOCK();
* ... record result ...
* NAN_DATA_UNLOCK();
*
* Several past bug-fix commits on this branch (`fix(nan): release
* NAN_DATA_LOCK before initiator datapath_req`, `fix(nan): unlock
* NAN_DATA on all paths in ndp_indication_cb`) trace back to forgotten
* unlocks; new sites that call the blob must follow this pattern.
*/
extern void *s_nan_data_lock;
#define NAN_DATA_LOCK() os_mutex_lock(s_nan_data_lock)
#define NAN_DATA_UNLOCK() os_mutex_unlock(s_nan_data_lock)
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
/* NAN 4-way handshake constants (RSNA key descriptor layout) */
#define NAN_NONCE_LEN 32
#define NAN_REPLAY_COUNTER_LEN 8
#define NAN_KEY_RSC_LEN 8
#define NAN_KEY_MIC_LEN 16
/* KCK/KEK/TK buffer sizes are sized for the largest cipher suite the host
* could potentially run (NCS-SK-256 KCK=24, KEK=32, TK=32). Only NCS-SK-128
* is wired through M1–M4 today (see nan_get_first_csid) so the *_set fields
* mark the live length in the buffer. */
#define NAN_ND_KCK_MAX_LEN 24
#define NAN_ND_KEK_MAX_LEN 32
#define NAN_ND_TK_MAX_LEN 32
#define NAN_GTK_MAX_LEN 32
#define NAN_AUTH_TOKEN_MAX_LEN 24
/* RSNA Key Descriptor offsets (same as 802.11 EAPOL-Key) */
#define NAN_KEY_DESC_TYPE_OFF 0
#define NAN_KEY_DESC_KEY_INFO_OFF 1
#define NAN_KEY_DESC_KEY_LEN_OFF 3
#define NAN_KEY_DESC_REPLAY_OFF 5
#define NAN_KEY_DESC_NONCE_OFF 13
#define NAN_KEY_DESC_IV_OFF 45
#define NAN_KEY_DESC_RSC_OFF 61
#define NAN_KEY_DESC_KEY_ID_OFF 69
#define NAN_KEY_DESC_MIC_OFF 77
#define NAN_KEY_DESC_DATA_LEN_OFF 93
#define NAN_KEY_DESC_DATA_OFF 95
#define NAN_KEY_DESC_MIN_LEN 95
/* Key Descriptor Type (same as 802.11 EAPOL-Key) */
#define NAN_KEY_DESC_TYPE_RSN 2
/* Security Context Identifier (SCID) types (Table 123) */
#define NAN_SEC_CTX_TYPE_ND_PMKID 1
/* Key Info bits (same semantics as RSNA) */
#define NAN_KEY_INFO_MIC BIT(8)
#define NAN_KEY_INFO_SECURE BIT(9)
#define NAN_KEY_INFO_INSTALL BIT(6)
#define NAN_KEY_INFO_ACK BIT(7)
#define NAN_KEY_INFO_ENC_KEY BIT(12)
#define NAN_KEY_INFO_KEY_TYPE BIT(3) /* 1=Pairwise, 0=Group */
/* NCS-SK-128 only for now (Table 21): KCK 128 bits, KEK 128 bits, TK 128 bits, MIC 16 bytes */
#define NAN_NCS_SK_128_KCK_LEN 16
#define NAN_NCS_SK_128_KEK_LEN 16
#define NAN_NCS_SK_128_TK_LEN 16
#define NAN_NCS_SK_128_MIC_LEN 16
#define NAN_NCS_SK_128_PTK_LEN (NAN_NCS_SK_128_KCK_LEN + NAN_NCS_SK_128_KEK_LEN + NAN_NCS_SK_128_TK_LEN)
/* Internal key-install constants matching esp_wifi_set_sta_key_internal semantics. */
#define NAN_WIFI_WPA_ALG_CCMP 3
#define NAN_KEY_FLAG_RX BIT(2)
#define NAN_KEY_FLAG_TX BIT(3)
#define NAN_KEY_FLAG_PAIRWISE BIT(5)
/* Handshake state */
enum nan_handshake_state {
NAN_HANDSHAKE_IDLE = 0,
NAN_HANDSHAKE_M1_SENT, /* Initiator: sent NDP Request (M1) */
NAN_HANDSHAKE_M1_RCVD,
NAN_HANDSHAKE_M2_SENT,
NAN_HANDSHAKE_M2_RCVD,
NAN_HANDSHAKE_M3_SENT,
NAN_HANDSHAKE_M3_PENDING_VERIFY, /* Responder: M3 parsed, awaits Auth_Token||body MIC verify */
NAN_HANDSHAKE_M3_RCVD,
NAN_HANDSHAKE_M4_RCVD,
NAN_HANDSHAKE_COMPLETE
};
#endif /* CONFIG_ESP_WIFI_NAN_SECURITY */
/* Per-peer service info */
struct peer_svc_info {
SLIST_ENTRY(peer_svc_info) next;
uint8_t peer_svc_info[ESP_WIFI_MAX_SVC_INFO_LEN];
uint8_t svc_id;
uint8_t own_svc_id;
uint8_t type;
uint8_t peer_nmi[MACADDR_LEN];
uint32_t device_caps;
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
/* Per-(own_svc, peer) result of subscriber-side PMKID match at SDF RX.
* 0xFF = no match; otherwise the index into own_svc->user_cfg.creds[]
* whose ND-PMKID matched the publisher SCIA. The initiator NDP-req path
* uses this to pick the right credential for M1's pair-PMKID. */
uint8_t matched_cred_idx;
#endif
};
/* Own (locally registered) service info */
struct own_svc_info {
char svc_name[ESP_WIFI_MAX_SVC_NAME_LEN];
uint8_t svc_id;
uint8_t type;
bool ndp_resp_needed;
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
/* 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;
};
/* Per-NDP link state */
struct ndl_info {
uint8_t ndp_id;
uint8_t peer_ndi[MACADDR_LEN];
uint8_t peer_nmi[MACADDR_LEN];
uint8_t publisher_id;
uint8_t own_role;
uint32_t device_caps;
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
wifi_nan_security_params_t security_ctx;
uint8_t anonce[NAN_NONCE_LEN];
uint8_t snonce[NAN_NONCE_LEN];
uint8_t nd_kck[NAN_ND_KCK_MAX_LEN];
uint8_t nd_kek[NAN_ND_KEK_MAX_LEN];
uint8_t nd_tk[NAN_ND_TK_MAX_LEN];
uint8_t kck_len;
uint8_t kek_len;
uint8_t tk_len;
uint8_t ptk_set: 1;
uint8_t ptk_reserved: 7;
uint8_t tx_replay_counter[NAN_REPLAY_COUNTER_LEN];
uint8_t rx_replay_counter[NAN_REPLAY_COUNTER_LEN];
uint8_t rx_replay_counter_set: 1;
uint8_t replay_reserved: 7;
uint8_t auth_token[NAN_AUTH_TOKEN_MAX_LEN];
uint8_t auth_token_len;
uint8_t handshake_state;
/* Group key state (unsupported -- pairwise-only M1-M4 flow today;
* fields kept to match the spec-defined RSNA key descriptor layout
* and stay forward-compatible). */
uint8_t gtk[NAN_GTK_MAX_LEN];
uint8_t igtk[NAN_GTK_MAX_LEN];
uint8_t bigtk[NAN_GTK_MAX_LEN];
uint8_t gtk_len;
uint8_t igtk_len;
uint8_t bigtk_len;
uint8_t gtk_set: 1;
uint8_t igtk_set: 1;
uint8_t bigtk_set: 1;
uint8_t group_keys_reserved: 5;
uint8_t key_rsc[NAN_KEY_RSC_LEN];
#endif
};
/* NAN context shared between files */
typedef struct {
uint8_t state;
uint8_t event;
struct ndl_info ndl[ESP_WIFI_NAN_DATAPATH_MAX_PEERS];
struct own_svc_info own_svc[ESP_WIFI_NAN_MAX_SVC_SUPPORTED];
esp_netif_t *nan_netif;
} nan_ctx_t;
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);
/* === nan_secure_dp_funcs initializer targets === */
/* Always-present (defined in nan_app.c) */
void esp_nan_ndp_tx_done_cb(uint8_t ndp_id, const uint8_t *peer_nmi,
uint8_t msg_type, bool tx_status);
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
/* Security-gated (defined in nan_security.c) */
uint32_t esp_nan_get_csia_len(uint16_t own_csid_bitmap, uint16_t peer_csid_bitmap);
uint32_t esp_nan_get_scia_len(uint8_t num_pmkids);
uint32_t esp_nan_get_shared_key_desc_attr_len(uint16_t key_data_len);
int esp_nan_ndp_security_install_get_shared_desc_len(void);
int esp_nan_construct_csia(uint8_t *frm, uint8_t pub_id,
uint16_t own_csid_bitmap, uint16_t peer_csid_bitmap);
int esp_nan_construct_scia_publish(uint8_t *frm, uint8_t pub_id,
uint8_t num_pmkids,
const uint8_t pmkids[][ESP_WIFI_NAN_NDP_PMKID_LEN]);
int esp_nan_construct_scia_ndp_req(uint8_t *frm, uint8_t ndp_id,
const uint8_t *peer_nmi);
int esp_nan_construct_scia_ndp_resp(uint8_t *frm, uint8_t ndp_id,
const uint8_t *peer_nmi);
int esp_nan_get_ndp_req_shared_key_desc(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_get_ndp_resp_shared_key_desc(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_get_ndp_confirm_shared_key_desc(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_get_ndp_security_install_key_desc(uint8_t *buf, size_t buf_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_capture_m1_auth_token(const uint8_t *m1_body, size_t body_len,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_update_ndp_resp_mic(uint8_t *m2_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_update_ndp_confirm_mic(uint8_t *m3_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_update_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);
int esp_nan_verify_ndp_resp_mic(uint8_t *m2_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_verify_ndp_confirm_mic(uint8_t *m3_body, size_t body_len,
uint8_t *key_desc_attr,
uint8_t ndp_id, const uint8_t *peer_nmi);
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_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_peer_sdf_security_t *security);
/* Helpers defined in nan_security.c, used by nan_app.c */
/*
* Apply any pending CSIA/SCIA/M1 state captured by the NDP key-desc parser
* onto the freshly-created NDL. Called from nan_app_ndp_indication_cb once
* (ndp_id, peer_nmi, peer_ndi, p_own_svc) are all known.
*/
void nan_security_apply_pending(struct ndl_info *ndl,
struct own_svc_info *p_own_svc,
uint8_t pub_id,
const uint8_t *peer_nmi,
const uint8_t *peer_ndi);
/* 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
* the initiator pre-claim window (peer-only lookup). */
uint16_t nan_get_ndp_security_csid(uint8_t ndp_id, const uint8_t *peer_nmi);
/* Subscribe path: populate the initiator NDL's security_ctx (cipher + pair-PMKID +
* ND-PMK) from the local subscribe's own_svc and the SDF-time matched credential
* recorded on peer_svc. The blob's CSIA / SCIA / Shared-Key-Descriptor builder
* callbacks look up security keyed by (ndp_id, peer_nmi). No-op if the local
* subscribe didn't request encrypted datapath. */
esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl,
const struct own_svc_info *own_svc,
const struct peer_svc_info *peer_svc,
const uint8_t *peer_nmi);
/*
* Compare locally derived ND-PMKID (subscriber passphrase, publisher NMI) to
* peer discovery security params. own_svc identifies the local subscribe
* (service_name + creds[]) and peer_svc is updated with matched_cred_idx on
* success. Returns true if any local cred's PMKID matches a peer-advertised one.
*/
bool nan_security_service_match(const struct own_svc_info *own_svc,
struct peer_svc_info *peer_svc,
const uint8_t *publisher_nmi,
const wifi_nan_peer_sdf_security_t *peer_sec);
#else
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)service_name;
(void)sec_cfg;
(void)out_derived;
return ESP_FAIL;
}
#endif /* CONFIG_ESP_WIFI_NAN_SECURITY */
#ifdef __cplusplus
}
#endif
File diff suppressed because it is too large Load Diff
@@ -1117,6 +1117,30 @@ int pbkdf2_sha1(const char *passphrase, const u8 *ssid, size_t ssid_len,
}
#endif /* defined(CONFIG_MBEDTLS_SHA1_C) || defined(CONFIG_MBEDTLS_HARDWARE_SHA) */
#if defined(MBEDTLS_PKCS5_C) && defined(MBEDTLS_MD_C)
#include "mbedtls/private/pkcs5.h"
#if defined(MBEDTLS_SHA256_C)
int pbkdf2_sha256(const char *passphrase, const u8 *salt, size_t salt_len,
int iterations, u8 *buf, size_t buflen)
{
int ret;
if (!passphrase || !salt || !buf || !salt_len || !buflen || iterations <= 0) {
return -1;
}
ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA256,
(const u8 *) passphrase,
os_strlen(passphrase),
salt, salt_len,
(unsigned int) iterations, (uint32_t) buflen, buf);
return ret == 0 ? 0 : -1;
}
#endif /* MBEDTLS_SHA256_C */
#endif /* MBEDTLS_PKCS5_C && MBEDTLS_MD_C */
#ifdef MBEDTLS_DES_C
int des_encrypt(const u8 *clear, const u8 *key, u8 *cypher)
{
@@ -26,5 +26,7 @@ int tls_prf_sha256(const u8 *secret, size_t secret_len,
int hmac_sha256_kdf(const u8 *secret, size_t secret_len,
const char *label, const u8 *seed, size_t seed_len,
u8 *out, size_t outlen);
int pbkdf2_sha256(const char *passphrase, const u8 *salt, size_t salt_len,
int iterations, u8 *buf, size_t buflen);
#endif /* SHA256_H */
@@ -23,4 +23,44 @@ menu "Example Configuration"
help
Send a reply to the Follow Up sent by a Subscriber
menu "Security Configuration"
config EXAMPLE_NAN_SECURITY_ENABLED
bool "Enable NAN Security"
depends on ESP_WIFI_NAN_SECURITY
default y
help
Enable encrypted NAN datapath (NDP Security) for this example.
When enabled the publisher advertises a secured service and
accepts only encrypted NDP setups from subscribers that share
the same credential (passphrase or PMK).
Disable to advertise an open (unencrypted) service.
choice EXAMPLE_NAN_SECURITY_METHOD
prompt "Security Method"
depends on EXAMPLE_NAN_SECURITY_ENABLED
default EXAMPLE_NAN_SEC_METHOD_PASSPHRASE
help
Choose the method for NAN discovery security.
config EXAMPLE_NAN_SEC_METHOD_PASSPHRASE
bool "Passphrase-based (Password)"
config EXAMPLE_NAN_SEC_METHOD_PMK
bool "Direct PMK (32-byte hex)"
endchoice
config EXAMPLE_NAN_PASSPHRASE
string "Passphrase"
depends on EXAMPLE_NAN_SEC_METHOD_PASSPHRASE
default "password"
help
Passphrase for NAN Discovery security.
config EXAMPLE_NAN_PMK
string "Direct PMK (Hex)"
depends on EXAMPLE_NAN_SEC_METHOD_PMK
default "ee3585063056d164d15454ad39010d4e2640b0d82fb24a2d6899862d273c68bf"
help
32-byte hex string representing the PMK.
endmenu
endmenu
@@ -11,6 +11,7 @@
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -37,6 +38,23 @@
static EventGroupHandle_t nan_event_group;
static const char *TAG = "publisher";
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
static bool decode_hex_string(const char *hex, uint8_t *out, size_t out_len)
{
if (!hex || !out || strlen(hex) != out_len * 2) {
return false;
}
for (size_t i = 0; i < out_len; i++) {
unsigned int byte;
if (sscanf(hex + 2 * i, "%2x", &byte) != 1) {
return false;
}
out[i] = (uint8_t)byte;
}
return true;
}
#endif
static int NAN_RECEIVE = BIT0;
uint8_t g_peer_inst_id;
static uint8_t g_peer_mac[ETH_ALEN];
@@ -105,10 +123,38 @@ void wifi_nan_publish(void)
.matching_filter = EXAMPLE_NAN_MATCHING_FILTER,
.single_replied_event = 1,
/* 0 - All incoming NDP requests will be internally accepted,
1 - All incoming NDP requests raise NDP_INDICATION event and require esp_wifi_nan_datapath_resp to accept or reject. */
1 - All incoming NDP requests raise NDP_INDICATION event and require esp_wifi_nan_datapath_resp to accept or reject.
This example uses 1 to exercise nan_ndp_indication_event_handler(). */
.ndp_resp_needed = 1,
.datapath_reqd = 1,
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
.security_reqd = 1,
#endif
};
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
wifi_nan_discovery_security_params_t security_cfg = {
.num_credentials = 1,
.creds = {
{
.csid = WIFI_NAN_CSID_NCS_SK_128,
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
.use_pmk = true,
#else
.use_pmk = false,
.passphrase = CONFIG_EXAMPLE_NAN_PASSPHRASE,
#endif
},
},
};
publish_cfg.security_cfg = &security_cfg;
#endif
#if defined(CONFIG_EXAMPLE_NAN_SECURITY_ENABLED) && defined(CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK)
if (!decode_hex_string(CONFIG_EXAMPLE_NAN_PMK, security_cfg.creds[0].pmk,
sizeof(security_cfg.creds[0].pmk))) {
ESP_LOGE(TAG, "Failed to decode CONFIG_EXAMPLE_NAN_PMK");
return;
}
#endif
pub_id = esp_wifi_nan_publish_service(&publish_cfg);
if (pub_id == 0) {
@@ -33,4 +33,44 @@ menu "Example Configuration"
help
Send a message to the Publisher using NAN Follow Up
menu "Security Configuration"
config EXAMPLE_NAN_SECURITY_ENABLED
bool "Enable NAN Security"
depends on ESP_WIFI_NAN_SECURITY
default y
help
Enable encrypted NAN datapath (NDP Security) for this example.
When enabled the subscriber only matches secured Publish SDFs
whose PMKID is derivable from the credential configured below
(passphrase or PMK) and sets up an encrypted NDP.
Disable to discover open (unencrypted) services.
choice EXAMPLE_NAN_SECURITY_METHOD
prompt "Security Method"
depends on EXAMPLE_NAN_SECURITY_ENABLED
default EXAMPLE_NAN_SEC_METHOD_PASSPHRASE
help
Choose the method for NAN discovery security.
config EXAMPLE_NAN_SEC_METHOD_PASSPHRASE
bool "Passphrase-based (Password)"
config EXAMPLE_NAN_SEC_METHOD_PMK
bool "Direct PMK (32-byte hex)"
endchoice
config EXAMPLE_NAN_PASSPHRASE
string "Passphrase"
depends on EXAMPLE_NAN_SEC_METHOD_PASSPHRASE
default "password"
help
Passphrase for NAN Discovery security. Must match the publisher.
config EXAMPLE_NAN_PMK
string "Direct PMK (Hex)"
depends on EXAMPLE_NAN_SEC_METHOD_PMK
default "ee3585063056d164d15454ad39010d4e2640b0d82fb24a2d6899862d273c68bf"
help
32-byte hex string representing the PMK. Must match the publisher.
endmenu
endmenu
@@ -37,6 +37,29 @@
static const char *TAG = "subscriber";
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
/* Decode lowercase hex string into a fixed-size byte buffer. Returns true on
* success. Mirrors the helper in the publisher example so both endpoints
* derive the same PMK from the same hex input. */
static bool decode_hex_string(const char *hex, uint8_t *out, size_t out_len)
{
if (!hex || !out) {
return false;
}
if (strlen(hex) != out_len * 2) {
return false;
}
for (size_t i = 0; i < out_len; i++) {
unsigned int byte;
if (sscanf(hex + 2 * i, "%2x", &byte) != 1) {
return false;
}
out[i] = (uint8_t)byte;
}
return true;
}
#endif
static EventGroupHandle_t nan_event_group;
const int NAN_SERVICE_MATCH = BIT0;
@@ -45,7 +68,7 @@ const int NDP_FAILED = BIT2;
static wifi_event_nan_svc_match_t g_svc_match_evt;
static uint8_t s_ipv6_identifier[8] = {0};
static uint8_t s_ipv6_identifier[NAN_IPV6_IDENTIFIER_LEN] = {0};
static void nan_receive_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
@@ -60,6 +83,23 @@ static void nan_receive_event_handler(void *arg, esp_event_base_t event_base,
#ifdef CONFIG_EXAMPLE_NAN_SEND_PING
static uint8_t g_peer_ndi[ETH_ALEN];
static void nan_ndp_confirmed_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
wifi_event_ndp_confirm_t *evt = (wifi_event_ndp_confirm_t *)event_data;
if (evt->status == NDP_STATUS_REJECTED) {
ESP_LOGE(TAG, "NDP request to Peer "MACSTR" rejected [NDP ID - %d]", MAC2STR(evt->peer_nmi), evt->ndp_id);
xEventGroupSetBits(nan_event_group, NDP_FAILED);
} else {
memcpy(g_peer_ndi, evt->peer_ndi, sizeof(g_peer_ndi));
memcpy(s_ipv6_identifier, evt->ipv6_identifier, sizeof(evt->ipv6_identifier));
xEventGroupSetBits(nan_event_group, NDP_CONFIRMED);
}
}
#endif
#ifdef CONFIG_EXAMPLE_NAN_SEND_PING
static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint8_t ttl;
@@ -103,21 +143,6 @@ static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
esp_ping_delete_session(hdl);
}
static void nan_ndp_confirmed_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
wifi_event_ndp_confirm_t *evt = (wifi_event_ndp_confirm_t *)event_data;
if (evt->status == NDP_STATUS_REJECTED) {
ESP_LOGE(TAG, "NDP request to Peer "MACSTR" rejected [NDP ID - %d]", MAC2STR(evt->peer_nmi), evt->ndp_id);
xEventGroupSetBits(nan_event_group, NDP_FAILED);
} else {
memcpy(g_peer_ndi, evt->peer_ndi, sizeof(g_peer_ndi));
memcpy(s_ipv6_identifier, evt->ipv6_identifier, sizeof(evt->ipv6_identifier));
xEventGroupSetBits(nan_event_group, NDP_CONFIRMED);
}
}
static void ping_nan_peer(esp_netif_t *netif)
{
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
@@ -196,7 +221,34 @@ void wifi_nan_subscribe(void)
#endif
.matching_filter = EXAMPLE_NAN_MATCHING_FILTER,
.single_match_event = 1,
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
.security_reqd = 1,
#endif
};
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
wifi_nan_discovery_security_params_t security_cfg = {
.num_credentials = 1,
.creds = {
{
.csid = WIFI_NAN_CSID_NCS_SK_128,
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
.use_pmk = true,
#else
.use_pmk = false,
.passphrase = CONFIG_EXAMPLE_NAN_PASSPHRASE,
#endif
},
},
};
subscribe_cfg.security_cfg = &security_cfg;
#endif
#if defined(CONFIG_EXAMPLE_NAN_SECURITY_ENABLED) && defined(CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK)
if (!decode_hex_string(CONFIG_EXAMPLE_NAN_PMK, security_cfg.creds[0].pmk,
sizeof(security_cfg.creds[0].pmk))) {
ESP_LOGE(TAG, "Failed to decode CONFIG_EXAMPLE_NAN_PMK");
return;
}
#endif
sub_id = esp_wifi_nan_subscribe_service(&subscribe_cfg);
if (sub_id == 0) {
@@ -231,7 +283,8 @@ void wifi_nan_subscribe(void)
memcpy(ndp_req.peer_mac, g_svc_match_evt.pub_if_mac, sizeof(ndp_req.peer_mac));
esp_wifi_nan_datapath_req(&ndp_req);
EventBits_t bits_2 = xEventGroupWaitBits(nan_event_group, NDP_CONFIRMED, pdFALSE, pdFALSE, portMAX_DELAY);
EventBits_t bits_2 = xEventGroupWaitBits(nan_event_group, NDP_CONFIRMED | NDP_FAILED,
pdFALSE, pdFALSE, portMAX_DELAY);
if (bits_2 & NDP_CONFIRMED) {
vTaskDelay(5000 / portTICK_PERIOD_MS);
ping_nan_peer(nan_netif);