test(wifi): NAN PMK derivation crypto test

Unit-test the ND-PMK derivation path against known-good vectors using
the wpa_supplicant-ported pbkdf2_sha256.
This commit is contained in:
Sarvesh Bodakhe
2026-05-19 11:07:07 +05:30
parent 0244cb362b
commit 2da41618f5
8 changed files with 300 additions and 0 deletions
@@ -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