feat(wifi): NAN encrypted NDP example apps

nan_publisher / nan_subscriber demonstrate the secure NDP path:
publisher acts as the responder + UDP echo server, subscriber as the
initiator. Both support passphrase and pre-shared PMK modes via
menuconfig; PMK hex strings are decoded with the same helper on both
sides so endpoints derive matching PMKIDs.
This commit is contained in:
Sarvesh Bodakhe
2026-05-19 11:07:06 +05:30
parent 94f226d73b
commit 0244cb362b
4 changed files with 169 additions and 18 deletions
@@ -23,4 +23,38 @@ 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
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,30 @@ 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,
.security_cfg = {
.csid_bitmap = WIFI_NAN_CSID_BIT_NCS_SK_128,
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
.use_pmk = true,
#else
.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))) {
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,38 @@ 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
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_ADDR_ID_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,26 @@ 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,
.security_cfg = {
.csid_bitmap = WIFI_NAN_CSID_BIT_NCS_SK_128,
#ifdef CONFIG_EXAMPLE_NAN_SEC_METHOD_PMK
.use_pmk = true,
#else
.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))) {
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 +275,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);