feat(esp_wifi): Add NAN Pairing support

- Add container struct for internal extra params for follow-up
- Support for parsing Shared Key Desc in Pairing follow-up
- Implement NAN Pairing API's with required parameters
- In KeyData set cipher_ver to 0, Key Info to 0x12C8
  (AKM-defined | Pairwise | Install | ACK |
   Secure | Encrypted Key Data) for iOS compatibility
- Move NAN PASN into esp_nan_supplicant.c, move declarations
  to esp_private/esp_supp_nan.h
- Align PASN/ND-PMK derivation with hostap

Co-authored-by: Sajia <sajia.ali@espressif.com>
Co-authored-by: Akshat Agrawal <akshat.agrawal@espressif.com>
Co-authored-by: Sarvesh Bodakhe <sarvesh.bodakhe@espressif.com>
This commit is contained in:
Nachiket Kukade
2026-05-22 13:01:51 +08:00
co-authored by Sajia Akshat Agrawal Sarvesh Bodakhe
parent e731ff3598
commit 9f361f478d
21 changed files with 2065 additions and 647 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ set(esp_srcs "esp_supplicant/src/esp_eap_client.c"
"esp_supplicant/src/esp_wps.c"
"esp_supplicant/src/esp_wpa3.c"
"esp_supplicant/src/esp_owe.c"
"esp_supplicant/src/nan_pasn.c")
"esp_supplicant/src/esp_nan_supplicant.c")
if(CONFIG_ESP_WIFI_SOFTAP_SUPPORT)
set(esp_srcs ${esp_srcs} "esp_supplicant/src/esp_hostap.c")
endif()
@@ -0,0 +1,132 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Private bridge API between the WiFi driver / NAN discovery engine
* (components/esp_wifi/wifi_apps/nan_app) and the imported NAN
* NDP/NDL/bootstrap/security engine living in
* components/wpa_supplicant/src/nan/.
*
* Symbols declared here are implemented in
* components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
#include "esp_wifi_types_generic.h"
#ifdef __cplusplus
extern "C" {
#endif
struct nan_data;
typedef void (*esp_nan_pairing_key_installed_cb_t)(const uint8_t *peer_nmi);
#if CONFIG_ESP_WIFI_PASN_SUPPORT
#ifndef ETH_ALEN
#define ETH_ALEN 6
#endif
#ifndef NAN_PASN_GLOBAL_PTK_BLOB_MAX
#define NAN_PASN_GLOBAL_PTK_BLOB_MAX 128
#endif
#ifndef NAN_PASN_KEY_PMK_MAX
#define NAN_PASN_KEY_PMK_MAX 64
#endif
#ifndef NAN_PASN_KEK_MAX_LEN
#define NAN_PASN_KEK_MAX_LEN 32
#endif
#ifndef NAN_PASN_NIK_LEN
#define NAN_PASN_NIK_LEN 16
#endif
enum nan_role {
NAN_ROLE_IDLE = 0,
NAN_ROLE_PAIRING_INITIATOR = 1,
NAN_ROLE_PAIRING_RESPONDER = 2,
};
/**
* Last PASN key material after successful pairing (PMK + flattened PTK KCK|KEK|TK|KDK).
* Written before @c pasn PTK is cleared; initiator session is torn down on Auth3 TX status.
*/
struct nan_pasn_key_material {
uint8_t valid;
uint8_t peer_addr[ETH_ALEN];
enum nan_role role;
int akmp;
int cipher;
size_t pmk_len;
uint8_t pmk[NAN_PASN_KEY_PMK_MAX];
size_t ptk_blob_len;
uint8_t ptk_blob[NAN_PASN_GLOBAL_PTK_BLOB_MAX];
size_t kek_len;
uint8_t kek[NAN_PASN_KEK_MAX_LEN];
};
/**
* @brief Schedule NAN PASN responder setup after pairing bootstrapping indication.
*
* Invoked from @c nan_app_pairing_indication_cb when the local device is the
* pairing responder. Runs on the wpa_supplicant eloop thread.
*
* @param peer_nmi Peer NMI (6 bytes).
* @param pincode 6-digit PIN (0..999999), or @c UINT32_MAX for the default PIN.
* @param pairing_key_installed_cb Callback invoked after pairwise key installation with peer NMI.
* @return 0 on success, -1 on failure.
*/
int esp_nan_supp_pasn_responder_init(const uint8_t *peer_nmi, uint32_t pincode,
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb);
/**
* @brief Schedule NAN PASN initiator authentication after pairing bootstrapping confirm.
*
* Invoked from @c nan_app_pairing_confirm_cb when the local device is the
* pairing initiator and bootstrapping completed successfully (@a status == 0).
* Runs on the wpa_supplicant eloop thread.
*
* @param peer_nmi Peer NMI (6 bytes).
* @param pincode 6-digit PIN (0..999999), or @c UINT32_MAX for the default PIN.
* @param pairing_key_installed_cb Callback invoked after pairwise key installation with peer NMI.
* @return 0 on success, -1 on failure.
*/
int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode,
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb);
/**
* Schedule @ref handle_auth_pasn from NAN app callback table.
*/
void handle_auth_pasn(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t status);
const struct nan_pasn_key_material *nan_pasn_get_saved_keys(void);
void nan_pasn_clear_saved_keys(void);
/**
* Decrypt a NAN Shared Key Descriptor attribute received in a follow-up frame.
*
* @param[in] shared_key_attr Raw attribute buffer beginning with the
* NAN Shared Key Descriptor ID octet.
* @param[in] attr_total_len Total length including the 3-byte attribute header.
* @param[out] nik Buffer receiving the decrypted NIK.
* @param[in] nik_size Size of @a nik. Must be at least @c NAN_PASN_NIK_LEN.
* @param[out] cipher_ver Optional. Receives the NIK cipher version.
* @param[out] lifetime_sec Optional. Receives the NIK lifetime in seconds.
* @return 0 on success, -1 on failure.
*/
int nan_pasn_followup_decrypt_keys(const uint8_t *shared_key_attr,
size_t attr_total_len,
uint8_t *nik, size_t nik_size,
uint8_t *cipher_ver,
uint32_t *lifetime_sec);
#endif /* CONFIG_ESP_WIFI_PASN_SUPPORT */
#ifdef __cplusplus
}
#endif
@@ -1,143 +0,0 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* NAN PASN — public API and types for NSD / Wi-Fi Aware examples (e.g. esp-nsd udp_client).
* When CONFIG_ESP_WIFI_PASN_SUPPORT is off, nan_pasn.c still links stubs (no-op / -1)
* for this API so apps can call e.g. pasn_responder_init_eloop without a link error.
*/
#ifndef _NAN_PASN_H_
#define _NAN_PASN_H_
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
/* Self-contained: public consumers only get esp_supplicant/include. */
#ifndef ETH_ALEN
#define ETH_ALEN 6
#endif
#ifndef PMKID_LEN
#define PMKID_LEN 16
#endif
#ifndef NAN_PASN_GLOBAL_PTK_BLOB_MAX
#define NAN_PASN_GLOBAL_PTK_BLOB_MAX 128
#endif
#ifndef NAN_PASN_KEY_PMK_MAX
#define NAN_PASN_KEY_PMK_MAX 64
#endif
struct wpabuf;
struct pasn_data;
struct rsn_pmksa_cache;
enum nan_role {
NAN_ROLE_IDLE = 0,
NAN_ROLE_PAIRING_INITIATOR = 1,
NAN_ROLE_PAIRING_RESPONDER = 2,
};
struct nan_config {
uint8_t dev_addr[ETH_ALEN];
uint8_t pasn_type;
void *cb_ctx;
int (*set_pmksa)(void *ctx, const uint8_t *peer_addr, const uint8_t *pmkid);
int (*pasn_send_mgmt)(void *ctx, const uint8_t *data, size_t data_len, int noack,
unsigned int freq, unsigned int wait_ms);
int (*prepare_data_element)(void *ctx, const uint8_t *peer_addr);
int (*parse_data_element)(void *ctx, const uint8_t *data, size_t len);
int (*pasn_validate_pmkid)(void *ctx, const uint8_t *addr, const uint8_t *pmkid);
};
struct nan_pasn_data {
enum nan_role dev_role;
/** Last known unicast peer; used when @c addr is broadcast so PASN Auth1 DA is not ff:ff:ff:ff:ff:ff. */
uint8_t pasn_unicast_peer[ETH_ALEN];
/**
* NUL-terminated decimal PIN ('0'–'9' only).
* Same buffer is SAE password material (ASCII per digit) and @c pasn->password.
*/
char dev_sae_pin[64];
size_t dev_sae_pin_len;
struct nan_config *cfg;
struct rsn_pmksa_cache *initiator_pmksa;
struct rsn_pmksa_cache *responder_pmksa;
uint8_t pasn_ptk[128];
size_t pasn_ptk_len;
struct pasn_data *pasn;
};
#ifdef __cplusplus
extern "C" {
#endif
int nan_initiate_pasn_verify(struct nan_pasn_data *pd, const uint8_t *peer_addr,
int freq, int role, const uint8_t *bssid,
const uint8_t *ssid, size_t ssid_len);
int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const uint8_t *addr, int freq);
struct nan_pasn_data *nan_pasn_data_init(void);
void nan_pasn_data_deinit(struct nan_pasn_data *pd);
int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, int freq);
int nan_pasn_auth(struct nan_pasn_data **pd_out, const uint8_t *peer_addr, int freq);
/**
* Defer PASN auth initiation on eloop (same delayed pattern as @ref pasn_responder_init_eloop).
* Replaces the global NAN PASN object: deinits any existing @c esp_nan_app_get_pasn_data(),
* @ref nan_pasn_data_init, optional PIN override, then @ref nan_pasn_auth_initiate.
* Operating frequency is chosen internally (current NAN channel, or 2412 MHz fallback).
* @param pincode 6-digit PIN value (0..999999), e.g. @c 0 for @c "000000". @c UINT32_MAX to keep the default PIN from @ref nan_pasn_data_init.
*/
int nan_pasn_auth_eloop(const uint8_t *peer_addr, uint32_t pincode);
/**
* Schedule @ref nan_initiate_pasn_verify on wpa_supplicant eloop after @a secs / @a usecs.
* Looks up @c struct nan_pasn_data via @ref esp_nan_app_get_pasn_data in the callback.
* @a bssid may be NULL to use @a peer_addr as BSSID. @a ssid may be NULL if @a ssid_len is 0.
*/
int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs,
const uint8_t *peer_addr, int freq, int role,
const uint8_t *bssid,
const uint8_t *ssid, size_t ssid_len);
/**
* NAN PASN responder setup (nan_pasn_data_init, esp_nan_app_set_pasn_data, PIN, nan_pasn_initialize).
* @param pincode 6-digit PIN value (e.g. @c wa_pairing_cred_t.pincode, 0..999999). Use @c UINT32_MAX to keep the default PIN from @ref nan_pasn_data_init (no override).
* Frequency is chosen internally (current NAN channel, or 2412 MHz fallback).
*/
int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode);
/**
* Schedule @ref pasn_responder_init on wpa_supplicant eloop (delay 0).
* @a peer_addr may be NULL (broadcast placeholder).
* @a pincode same as @ref pasn_responder_init (value is stored in eloop context).
*/
int pasn_responder_init_eloop(const uint8_t *peer_addr, uint32_t pincode);
void handle_auth_pasn(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t status);
/**
* Last PASN key material after successful pairing (PMK + flattened PTK KCK|KEK|TK|KDK).
* Written before @c pasn PTK is cleared; initiator session is torn down on Auth3 TX status.
*/
struct nan_pasn_key_material {
uint8_t valid;
uint8_t peer_addr[ETH_ALEN];
enum nan_role role;
int akmp;
int cipher;
size_t pmk_len;
uint8_t pmk[NAN_PASN_KEY_PMK_MAX];
size_t ptk_blob_len;
uint8_t ptk_blob[NAN_PASN_GLOBAL_PTK_BLOB_MAX];
};
const struct nan_pasn_key_material *nan_pasn_get_saved_keys(void);
void nan_pasn_clear_saved_keys(void);
#ifdef __cplusplus
}
#endif
#endif /* _NAN_PASN_H_ */
@@ -3042,8 +3042,10 @@ mbedtls_ecp_group_id ecc_group_from_psa(psa_ecc_family_t family,
switch (family) {
case PSA_ECC_FAMILY_SECP_R1:
switch (bits) {
#ifdef MBEDTLS_ECP_DP_SECP192R1_ENABLED
case 192:
return MBEDTLS_ECP_DP_SECP192R1;
#endif
case 256:
return MBEDTLS_ECP_DP_SECP256R1;
case 384:
@@ -3075,8 +3077,10 @@ mbedtls_ecp_group_id ecc_group_from_psa(psa_ecc_family_t family,
case PSA_ECC_FAMILY_SECP_K1:
switch (bits) {
#ifdef MBEDTLS_ECP_DP_SECP192K1_ENABLED
case 192:
return MBEDTLS_ECP_DP_SECP192K1;
#endif
case 256:
return MBEDTLS_ECP_DP_SECP256K1;
}
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "esp_private/esp_supp_nan.h"
struct wpabuf;
struct pasn_data;
struct rsn_pmksa_cache;
typedef void (*nan_pasn_pairing_key_installed_cb_t)(const uint8_t *peer_nmi);
struct nan_config {
uint8_t dev_addr[ETH_ALEN];
uint8_t pasn_type;
void *cb_ctx;
int (*set_pmksa)(void *ctx, const uint8_t *peer_addr, const uint8_t *pmkid);
int (*pasn_send_mgmt)(void *ctx, const uint8_t *data, size_t data_len, int noack,
unsigned int freq, unsigned int wait_ms);
int (*prepare_data_element)(void *ctx, const uint8_t *peer_addr);
int (*parse_data_element)(void *ctx, const uint8_t *data, size_t len);
int (*pasn_validate_pmkid)(void *ctx, const uint8_t *addr, const uint8_t *pmkid);
};
struct nan_pasn_data {
enum nan_role dev_role;
uint8_t pasn_unicast_peer[ETH_ALEN];
char dev_sae_pin[64];
size_t dev_sae_pin_len;
struct nan_config *cfg;
struct rsn_pmksa_cache *initiator_pmksa;
struct rsn_pmksa_cache *responder_pmksa;
uint8_t pasn_ptk[128];
size_t pasn_ptk_len;
struct pasn_data *pasn;
nan_pasn_pairing_key_installed_cb_t pairing_key_installed_cb;
};
int nan_initiate_pasn_verify(struct nan_pasn_data *pd, const uint8_t *peer_addr,
int freq, int role, const uint8_t *bssid,
const uint8_t *ssid, size_t ssid_len);
int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const uint8_t *addr, int freq);
struct nan_pasn_data *nan_pasn_data_init(void);
void nan_pasn_data_deinit(struct nan_pasn_data *pd);
int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, int freq);
int nan_pasn_auth(struct nan_pasn_data **pd_out, const uint8_t *peer_addr, int freq);
int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs,
const uint8_t *peer_addr, int freq, int role,
const uint8_t *bssid,
const uint8_t *ssid, size_t ssid_len);
@@ -7,7 +7,8 @@
*/
#include "sdkconfig.h"
#include "nan_pasn.h"
#include "esp_private/esp_supp_nan.h"
#include "esp_nan_supp_i.h"
#if CONFIG_ESP_WIFI_PASN_SUPPORT
@@ -17,6 +18,9 @@
#include "common/nan.h"
#include "esp_wifi_driver.h"
#include "crypto/crypto.h"
#include "crypto/aes_wrap.h"
#include "crypto/sha256.h"
#include "crypto/sha384.h"
#include "pasn/pasn_common.h"
#include "common/wpa_common.h"
@@ -27,6 +31,7 @@
#include "utils/eloop.h"
#include "esp_err.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_private/wifi.h"
#define IEEE80211_MGMT_HDRLEN 24
@@ -46,6 +51,135 @@ static struct nan_pasn_key_material g_nan_pasn_saved_keys;
/* Key index for esp_wifi_set_nan_key_internal (NAN PASN pairwise TK). */
int temp = 1;
#define NAN_PASN_AES_WRAP_OVERHEAD 8
#define NAN_PASN_AES_WRAP_MIN_CIPHERTEXT (NAN_PASN_AES_WRAP_OVERHEAD + 8)
/**
* Key lengths for NCS-PK-PASN-128 / NCS-PK-PASN-256 (Wi-Fi Aware pairing).
* Mirrors hostap @c nan_crypto_cipher_*_len in src/nan/nan_crypto.c.
*/
static size_t nan_pasn_cipher_kck_len(int cipher)
{
switch (cipher) {
case WPA_CIPHER_CCMP:
return 16;
case WPA_CIPHER_GCMP_256:
return 24;
default:
return 0;
}
}
static size_t nan_pasn_cipher_kek_len(int cipher)
{
switch (cipher) {
case WPA_CIPHER_CCMP:
return 16;
case WPA_CIPHER_GCMP_256:
return 32;
default:
return 0;
}
}
static size_t nan_pasn_cipher_tk_len(int cipher)
{
switch (cipher) {
case WPA_CIPHER_CCMP:
return 16;
case WPA_CIPHER_GCMP_256:
return 32;
default:
return 0;
}
}
static size_t nan_pasn_cipher_mic_len(int cipher)
{
switch (cipher) {
case WPA_CIPHER_CCMP:
return 16;
case WPA_CIPHER_GCMP_256:
return 24;
default:
return 0;
}
}
static size_t nan_pasn_cipher_eapol_key_hdrlen(int cipher)
{
size_t mic_len = nan_pasn_cipher_mic_len(cipher);
if (mic_len == 24) {
return sizeof(struct wpa_eapol_key_192);
}
if (mic_len == 16) {
return sizeof(struct wpa_eapol_key);
}
return 0;
}
static bool nan_pasn_cipher_is_supported(int cipher)
{
return cipher == WPA_CIPHER_CCMP || cipher == WPA_CIPHER_GCMP_256;
}
/**
* nan_crypto_derive_from_kdk - Derive a key from KDK using KDF-HASH-NNN
*
* KEY = KDF-HASH-NNN(KDK, label, Pairing Initiator NMI || Pairing Responder NMI)
*
* Mirrors hostap @c nan_crypto_derive_from_kdk (src/nan/nan_crypto.c).
* @a cipher is @c WPA_CIPHER_CCMP (NCS-PK-PASN-128) or @c WPA_CIPHER_GCMP_256
* (NCS-PK-PASN-256).
*/
static int nan_crypto_derive_from_kdk(const u8 *kdk, size_t kdk_len, int cipher,
const char *label,
const u8 *initiator_nmi,
const u8 *responder_nmi,
u8 *key, size_t key_len)
{
u8 data[ETH_ALEN * 2];
int ret;
if (!kdk || !kdk_len || !label || !initiator_nmi || !responder_nmi ||
!key || !key_len) {
wpa_printf(MSG_INFO,
"NAN: Invalid parameters for NPK/KEK derivation");
return -1;
}
os_memcpy(data, initiator_nmi, ETH_ALEN);
os_memcpy(data + ETH_ALEN, responder_nmi, ETH_ALEN);
if (cipher == WPA_CIPHER_CCMP) {
ret = sha256_prf(kdk, kdk_len, label, data, sizeof(data), key, key_len);
} else if (cipher == WPA_CIPHER_GCMP_256) {
ret = sha384_prf(kdk, kdk_len, label, data, sizeof(data), key, key_len);
} else {
wpa_printf(MSG_INFO,
"NAN: Unsupported cipher suite for key derivation: %d",
cipher);
return -1;
}
if (ret) {
wpa_printf(MSG_INFO,
"NAN: NPK/KEK derivation failed (ret=%d)", ret);
return ret;
}
wpa_hexdump_key(MSG_DEBUG, "NAN: KDK", kdk, kdk_len);
wpa_printf(MSG_DEBUG, "NAN: Label: %s", label);
wpa_printf(MSG_DEBUG, "NAN: Initiator NMI " MACSTR,
MAC2STR(initiator_nmi));
wpa_printf(MSG_DEBUG, "NAN: Responder NMI " MACSTR,
MAC2STR(responder_nmi));
wpa_hexdump_key(MSG_DEBUG, "NAN: Derived key", key, key_len);
return 0;
}
/** Same layout as @ref nan_pasn_store_ptk (KCK|KEK|TK|KDK). */
static int nan_pasn_flatten_ptk_blob(struct wpa_ptk *ptk, u8 *dst, size_t dst_sz,
size_t *out_len)
@@ -83,38 +217,40 @@ static int nan_pasn_flatten_ptk_blob(struct wpa_ptk *ptk, u8 *dst, size_t dst_sz
* Install PASN pairwise TK into the NAN interface key table (firmware).
* Call while @a pasn still holds a valid PTK (before forced_memzero).
*/
static void nan_pasn_install_nan_pairwise_tk(struct pasn_data *pasn)
static int nan_pasn_install_nan_pairwise_tk(struct nan_pasn_data *nan, struct pasn_data *pasn)
{
struct wpa_ptk *ptk;
uint8_t key_rsc[8] = {0};
uint8_t peer[ETH_ALEN];
int kret;
(void)nan;
if (!pasn) {
return;
return -1;
}
if (pasn->cipher != WPA_CIPHER_CCMP) {
if (!nan_pasn_cipher_is_supported(pasn->cipher)) {
wpa_printf(MSG_INFO, "NAN PASN: skip NAN TK install (cipher=%d)", pasn->cipher);
return;
return -1;
}
ptk = pasn_get_ptk(pasn);
if (!ptk || !ptk->tk_len || ptk->tk_len > sizeof(ptk->tk)) {
return;
if (!ptk || !ptk->tk_len || ptk->tk_len != nan_pasn_cipher_tk_len(pasn->cipher) ||
ptk->tk_len > sizeof(ptk->tk)) {
return -1;
}
os_memcpy(peer, pasn->peer_addr, ETH_ALEN);
wpa_hexdump_key(MSG_INFO, "NAN PASN: TK before esp_wifi_set_nan_key_internal",
ptk->tk, ptk->tk_len);
ESP_LOG_BUFFER_HEXDUMP("NAN PASN: NM-TK",
ptk->tk, ptk->tk_len, ESP_LOG_INFO);
kret = esp_wifi_set_nan_key_internal(
NAN_PASN_WIFI_ALG_CCMP, peer, temp, 1, key_rsc, sizeof(key_rsc),
NAN_PASN_WIFI_ALG_CCMP, pasn->peer_addr, temp, 1, key_rsc, sizeof(key_rsc),
ptk->tk, ptk->tk_len,
NAN_PASN_KEY_FLAG_PAIRWISE | NAN_PASN_KEY_FLAG_RX | NAN_PASN_KEY_FLAG_TX);
NAN_KEY_NM_TK);
if (kret != 0) {
wpa_printf(MSG_WARNING, "NAN PASN: esp_wifi_set_nan_key_internal failed (%d)",
kret);
return -1;
}
return 0;
}
/**
@@ -150,47 +286,48 @@ static void nan_pasn_post_pasn_pairing_indication_evt(struct nan_pasn_data *nan,
}
/**
* Common path for @ref esp_nan_app_post_pasn_pairing_confirm (initiator and responder).
* nan_crypto_derive_kek - Derive KEK from NM-KDK after PASN pairing
*
* @param auth_frame_status_code IEEE 802.11 Authentication @c status_code from the RX frame (host endian).
* @param initiator_nmi Initiator NMI (6 octets).
* @param responder_nmi Responder NMI (6 octets).
* @param require_pasn_internal_success If true, post only when @c pasn->status is @c WLAN_STATUS_SUCCESS
* (initiator after Auth2). Responder uses false.
* NM-KEK = KDF-HASH-MMM(NM-KDK, "NAN Management KEK Derivation",
* Pairing Initiator NMI || Pairing Responder NMI)
*
* Mirrors hostap @c nan_crypto_derive_kek (src/nan/nan_crypto.c). Called from
* @ref nan_pasn_copy_keys_from_pasn when @c ptk->kdk_len is set.
*/
static void nan_pasn_post_pasn_pairing_confirm_evt(struct nan_pasn_data *nan,
struct pasn_data *pasn,
u16 auth_frame_status_code,
const u8 initiator_nmi[ETH_ALEN],
const u8 responder_nmi[ETH_ALEN],
bool require_pasn_internal_success)
static int nan_crypto_derive_kek(const u8 *kdk, size_t kdk_len, int cipher,
const u8 *initiator_nmi,
const u8 *responder_nmi, struct wpa_ptk *ptk)
{
#if 0
wifi_event_nan_pasn_pairing_confirm_t conf;
const char *label = "NAN Management KEK Derivation";
size_t kek_len;
if (!nan || !pasn) {
return;
}
if (require_pasn_internal_success && pasn->status != WLAN_STATUS_SUCCESS) {
return;
wpa_printf(MSG_DEBUG, "NAN: Deriving KEK from NM-KDK");
if (!kdk || !kdk_len || !initiator_nmi || !responder_nmi || !ptk) {
wpa_printf(MSG_INFO,
"NAN: Invalid parameters for KEK derivation");
return -1;
}
os_memset(&conf, 0, sizeof(conf));
conf.type = WIFI_NAN_PASN_PAIRING_IND_TYPE_SETUP;
conf.status = WIFI_NAN_PASN_PAIRING_CONFIRM_STATUS_ACCEPTED;
conf.self_handle = 0;
conf.reason_code =
(auth_frame_status_code == WLAN_STATUS_SUCCESS) ? 0
: (uint8_t)auth_frame_status_code;
os_memcpy(conf.initiator_nan_address, initiator_nmi, ETH_ALEN);
os_memcpy(conf.responder_nan_address, responder_nmi, ETH_ALEN);
conf.paired_peer_handle_valid = 0;
conf.auth_password = nan->dev_sae_pin_len > 0 ? 1 : 0;
conf.auth_opportunistic =
(pasn->akmp == WPA_KEY_MGMT_PASN && nan->dev_sae_pin_len == 0) ? 1 : 0;
conf.npk_nik_caching = 0;
esp_nan_app_post_pasn_pairing_confirm(&conf);
#endif
if (!nan_pasn_cipher_is_supported(cipher)) {
wpa_printf(MSG_INFO,
"NAN: Unsupported cipher suite for KEK derivation: %d",
cipher);
return -1;
}
kek_len = nan_pasn_cipher_kek_len(cipher);
if (kek_len > sizeof(ptk->kek)) {
wpa_printf(MSG_INFO,
"NAN: KEK length %zu exceeds wpa_ptk buffer", kek_len);
return -1;
}
ptk->kek_len = kek_len;
return nan_crypto_derive_from_kdk(kdk, kdk_len, cipher, label,
initiator_nmi, responder_nmi,
ptk->kek, ptk->kek_len);
}
static void nan_pasn_copy_keys_from_pasn(struct nan_pasn_data *nan, struct pasn_data *pasn)
@@ -222,6 +359,34 @@ static void nan_pasn_copy_keys_from_pasn(struct nan_pasn_data *nan, struct pasn_
g_nan_pasn_saved_keys.pmk_len = pmk_len;
}
/*
* NAN Management KEK is derived from KDK using pairing initiator/responder
* NMI addresses (nan_crypto_derive_kek), same pattern as hostap. ND-PMK is
* derived earlier via pasn_nd_pmk_derive_from_kdk_store() (hostap
* nan_crypto_derive_nd_pmk_from_kdk) after pasn_pmk_to_ptk.
*/
if (ptk->kdk_len) {
const u8 *initiator_nmi;
const u8 *responder_nmi;
if (nan->dev_role == NAN_ROLE_PAIRING_INITIATOR) {
initiator_nmi = pasn->own_addr;
responder_nmi = pasn->peer_addr;
} else {
initiator_nmi = pasn->peer_addr;
responder_nmi = pasn->own_addr;
}
if (nan_crypto_derive_kek(ptk->kdk, ptk->kdk_len, pasn->cipher,
initiator_nmi, responder_nmi, ptk) != 0) {
wpa_printf(MSG_INFO, "NAN PASN: KEK derivation failed");
forced_memzero(&g_nan_pasn_saved_keys, sizeof(g_nan_pasn_saved_keys));
return;
}
ptk->ptk_len = ptk->kck_len + ptk->kek_len + ptk->tk_len + ptk->kdk_len;
}
if (nan_pasn_flatten_ptk_blob(ptk, g_nan_pasn_saved_keys.ptk_blob,
sizeof(g_nan_pasn_saved_keys.ptk_blob),
&g_nan_pasn_saved_keys.ptk_blob_len) != 0) {
@@ -229,6 +394,11 @@ static void nan_pasn_copy_keys_from_pasn(struct nan_pasn_data *nan, struct pasn_
return;
}
if (ptk->kek_len && ptk->kek_len <= sizeof(g_nan_pasn_saved_keys.kek)) {
os_memcpy(g_nan_pasn_saved_keys.kek, ptk->kek, ptk->kek_len);
g_nan_pasn_saved_keys.kek_len = ptk->kek_len;
}
g_nan_pasn_saved_keys.valid = 1;
}
@@ -242,6 +412,336 @@ void nan_pasn_clear_saved_keys(void)
forced_memzero(&g_nan_pasn_saved_keys, sizeof(g_nan_pasn_saved_keys));
}
/* NAN KDE OUI Type values from Wi-Fi Aware spec v4.0, Table 126. */
#define NAN_PASN_KDE_OUI_TYPE_NIK 36
#define NAN_PASN_KDE_OUI_TYPE_LIFETIME 37
#define NAN_PASN_KEY_LIFETIME_NIK_BIT BIT(3)
/**
* Decrypt NAN key data using AES Key Unwrap (RFC 3394).
*
* Ported from hostap @c nan_crypto_decrypt_key_data (src/nan/nan_crypto.c).
* Caller is responsible for freeing the returned wpabuf using @c wpabuf_free.
*
*/
static struct wpabuf *
nan_crypto_decrypt_key_data(const u8 *kek, size_t kek_len,
const u8 *encrypted_data, size_t encrypted_len)
{
struct wpabuf *decrypted;
size_t plain_len;
u8 *buf;
if (!encrypted_data || !encrypted_len) {
wpa_printf(MSG_INFO, "NAN: Invalid encrypted key data");
return NULL;
}
wpa_hexdump_key(MSG_DEBUG, "NAN: Encrypted key data",
encrypted_data, encrypted_len);
if (!kek || !kek_len) {
wpa_printf(MSG_INFO,
"NAN: No KEK available for key data decryption");
return NULL;
}
wpa_hexdump_key(MSG_DEBUG, "NAN: KEK for decryption", kek, kek_len);
/* AES-WRAP adds 8 bytes overhead and requires 8-byte aligned input. */
if (encrypted_len < NAN_PASN_AES_WRAP_MIN_CIPHERTEXT ||
encrypted_len % 8 != 0) {
wpa_printf(MSG_INFO,
"NAN: Invalid encrypted key data length %zu",
encrypted_len);
return NULL;
}
plain_len = encrypted_len - 8;
decrypted = wpabuf_alloc(plain_len);
if (!decrypted) {
wpa_printf(MSG_INFO,
"NAN: Failed to allocate decryption buffer");
return NULL;
}
buf = wpabuf_put(decrypted, plain_len);
if (aes_unwrap(kek, kek_len, plain_len / 8, encrypted_data, buf)) {
wpa_printf(MSG_INFO,
"NAN: AES unwrap failed - could not decrypt key data");
wpabuf_free(decrypted);
return NULL;
}
wpa_hexdump_key(MSG_DEBUG, "NAN: Decrypted key data",
wpabuf_head(decrypted), wpabuf_len(decrypted));
return decrypted;
}
/**
* Walk a sequence of NAN KDEs (Vendor Specific elements with WFA OUI) in the
* decrypted Key Data and copy the NIK and lifetime fields into output args.
*/
static int nan_pasn_parse_nik_kdes(const u8 *data, size_t len, int cipher,
u8 *nik, u8 *cipher_ver,
u32 *lifetime_sec, u16 *lifetime_bitmap)
{
size_t gtk_key_len = nan_pasn_cipher_tk_len(cipher);
size_t gtk_kde_min = 2 + 6 + gtk_key_len;
/* RSN KDE selectors. Defined locally so the parser stays usable even when
* the upstream macros are gated by CONFIG_IEEE80211W or are absent (BIGTK).
*/
static const u32 sel_igtk = RSN_SELECTOR(0x00, 0x0f, 0xac, 9);
static const u32 sel_bigtk = RSN_SELECTOR(0x00, 0x0f, 0xac, 14);
static const u8 wfa_oui[3] = { 0x50, 0x6f, 0x9a };
bool nik_found = false;
const u8 *pos = data;
const u8 *end = data + len;
while (pos + 2 <= end) {
u8 id = pos[0];
u8 elen = pos[1];
if (pos + 2 + elen > end) {
return -1;
}
if (id != WLAN_EID_VENDOR_SPECIFIC || elen < 4) {
pos += 2 + elen;
continue;
}
const u32 selector = RSN_SELECTOR_GET(pos + 2);
const u8 oui_type = pos[5];
const u8 *kde_body = pos + 6;
size_t kde_body_len = elen - 4;
if (os_memcmp(pos + 2, wfa_oui, sizeof(wfa_oui)) == 0) {
if (oui_type == NAN_PASN_KDE_OUI_TYPE_NIK &&
kde_body_len >= 1 + NAN_PASN_NIK_LEN) {
if (cipher_ver) {
*cipher_ver = kde_body[0];
}
os_memcpy(nik, kde_body + 1, NAN_PASN_NIK_LEN);
nik_found = true;
wpa_printf(MSG_DEBUG, "NAN: NIK KDE cipher_ver=%u",
kde_body[0]);
wpa_hexdump_key(MSG_DEBUG, "NAN: NIK",
kde_body + 1, NAN_PASN_NIK_LEN);
} else if (oui_type == NAN_PASN_KDE_OUI_TYPE_LIFETIME &&
kde_body_len >= 6) {
if (lifetime_bitmap) {
*lifetime_bitmap = WPA_GET_LE16(kde_body);
}
if (lifetime_sec) {
*lifetime_sec = WPA_GET_BE32(kde_body + 2);
}
}
} else if (gtk_key_len && selector == sel_igtk &&
kde_body_len >= gtk_kde_min) {
/* IGTK KDE: KeyID(2 LE) | IPN(6) | IGTK */
size_t igtk_len = kde_body_len - 8;
wpa_printf(MSG_DEBUG,
"NAN: IGTK KDE KeyID=%u igtk_len=%zu",
WPA_GET_LE16(kde_body), igtk_len);
wpa_hexdump(MSG_DEBUG, "NAN: IGTK IPN", kde_body + 2, 6);
wpa_hexdump_key(MSG_DEBUG, "NAN: IGTK",
kde_body + 8, igtk_len);
ESP_LOG_BUFFER_HEXDUMP("IGTK", kde_body + 8, igtk_len, ESP_LOG_INFO);
} else if (gtk_key_len && selector == sel_bigtk &&
kde_body_len >= gtk_kde_min) {
/* BIGTK KDE: KeyID(2 LE) | BIPN(6) | BIGTK */
size_t bigtk_len = kde_body_len - 8;
wpa_printf(MSG_DEBUG,
"NAN: BIGTK KDE KeyID=%u bigtk_len=%zu",
WPA_GET_LE16(kde_body), bigtk_len);
wpa_hexdump(MSG_DEBUG, "NAN: BIPN", kde_body + 2, 6);
wpa_hexdump_key(MSG_DEBUG, "NAN: BIGTK",
kde_body + 8, bigtk_len);
ESP_LOG_BUFFER_HEXDUMP("BIGTK", kde_body + 8, bigtk_len, ESP_LOG_INFO);
}
pos += 2 + elen;
}
/* Lifetime KDE is optional in practice (e.g. iPhone omits it), so only
* the NIK is required here.
*/
return nik_found ? 0 : -1;
}
/**
* Expected format of shared_key_attr (NCS-PK-PASN-128, MIC=16):
*
* Offset Size Field Source / spec ref
* ─────────────────────────────────────────────────────────────────────
* NAN attribute header (Wi-Fi Aware v4.0, Table 125)
* 0x00 1 Attribute ID = 0x24 NAN_ATTR_SHARED_KEY_DESCR
* 0x01 2 Attribute Length (LE) body length, not incl. header
* 0x03 1 Publish ID struct nan_shared_key.publish_id
*
* IEEE 802.11 RSNA Key Descriptor (EAPOL-Key body, IEEE 802.11-2020 §12.7.2)
* 0x04 1 Descriptor Type = 0x02 NAN_KEY_DESC (Wi-Fi Aware fixed)
* 0x05 2 Key Information (BE)
* 0x07 2 Key Length (BE) = 0x0000 not carrying a pairwise cipher key
* 0x09 8 Key Replay Counter unused for PASN one-shot
* 0x11 32 Key Nonce unused (no 4-way handshake)
* 0x31 16 EAPOL-Key IV unused
* 0x41 8 Key RSC unused
* 0x49 8 Reserved (Key ID)
* 0x51 16 Key MIC HMAC over body w/ MIC zeroed
* 0x61 2 Key Data Length (BE) length of the wrapped blob
* 0x63 N Key Data AES-WRAP(KEK, KDEs || pad)
*
* Total = 4 + 95 + N bytes.
*
* NCS-PK-PASN-128 (CCMP) and NCS-PK-PASN-256 (GCMP-256) use cipher-dependent
* KEK/MIC lengths (see @c nan_pasn_cipher_*_len).
*/
int nan_pasn_followup_decrypt_keys(const uint8_t *shared_key_attr,
size_t attr_total_len,
uint8_t *nik, size_t nik_size,
uint8_t *cipher_ver,
uint32_t *lifetime_sec)
{
const struct nan_pasn_key_material *saved;
const struct wpa_eapol_key *key_desc;
const u8 *body;
size_t body_len;
u16 attr_body_len;
u16 key_info;
u16 key_data_len;
u8 found_cipher_ver = 0;
u32 found_lifetime = 0;
u16 lifetime_bitmap = 0;
struct wpabuf *key_data = NULL;
size_t eapol_hdrlen;
size_t mic_len;
int ret = -1;
if (!shared_key_attr || !nik || nik_size < NAN_PASN_NIK_LEN) {
return -1;
}
/* Attribute header: ID(1) + Length(2 LE). */
if (attr_total_len < 3 || shared_key_attr[0] != NAN_ATTR_SHARED_KEY_DESCR) {
wpa_printf(MSG_INFO, "NAN: Invalid Shared Key Descriptor attribute");
return -1;
}
attr_body_len = WPA_GET_LE16(&shared_key_attr[1]);
if ((size_t)attr_body_len + 3 > attr_total_len) {
wpa_printf(MSG_INFO,
"NAN: Truncated Shared Key Descriptor attribute (len=%u, total=%zu)",
attr_body_len, attr_total_len);
return -1;
}
saved = nan_pasn_get_saved_keys();
if (!saved || !saved->kek_len) {
wpa_printf(MSG_INFO,
"NAN: No saved KEK available to decrypt Shared Key Descriptor");
return -1;
}
if (!nan_pasn_cipher_is_supported(saved->cipher)) {
wpa_printf(MSG_INFO,
"NAN: Unsupported cipher 0x%x for Shared Key Descriptor",
saved->cipher);
return -1;
}
eapol_hdrlen = nan_pasn_cipher_eapol_key_hdrlen(saved->cipher);
mic_len = nan_pasn_cipher_mic_len(saved->cipher);
if (!eapol_hdrlen || !mic_len) {
return -1;
}
/*
* Body layout (Wi-Fi Aware spec v4.0 + IEEE 802.11 EAPOL-Key):
* publish_id(1) + EAPOL-Key descriptor (MIC length per cipher) +
* key_data.
*/
if (attr_body_len < 1 + eapol_hdrlen) {
wpa_printf(MSG_INFO,
"NAN: Shared Key Descriptor body too short (%u, need %zu)",
attr_body_len, (size_t)(1 + eapol_hdrlen));
return -1;
}
body = &shared_key_attr[3];
body_len = attr_body_len;
/* Skip the 1-byte Publish ID; key descriptor starts at offset 1. */
key_desc = (const struct wpa_eapol_key *)(body + 1);
key_info = WPA_GET_BE16(key_desc->key_info);
if (!(key_info & WPA_KEY_INFO_KEY_TYPE)) {
wpa_printf(MSG_INFO,
"NAN: Follow-up frame does not contain pairwise key");
return -1;
}
if (!(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
wpa_printf(MSG_INFO,
"NAN: Follow-up frame does not contain encrypted key data");
return -1;
}
key_data_len = WPA_GET_BE16(key_desc->key_data_length);
if ((size_t)1 + eapol_hdrlen + key_data_len > body_len) {
wpa_printf(MSG_INFO,
"NAN: Shared Key Descriptor key data overruns attribute (key_data_len=%u, body_len=%zu, eapol_hdrlen=%zu)",
key_data_len, body_len, eapol_hdrlen);
return -1;
}
wpa_printf(MSG_DEBUG,
"NAN: Shared Key Descr cipher=%d mic_len=%zu key_data_len=%u body_len=%zu",
saved->cipher, mic_len, key_data_len, body_len);
key_data = nan_crypto_decrypt_key_data(saved->kek, saved->kek_len,
body + 1 + eapol_hdrlen,
key_data_len);
if (!key_data) {
wpa_printf(MSG_INFO,
"NAN: Failed to decrypt Shared Key Descriptor key data");
return -1;
}
ESP_LOG_BUFFER_HEXDUMP("Key Data", wpabuf_head(key_data), wpabuf_len(key_data), ESP_LOG_INFO);
if (nan_pasn_parse_nik_kdes(wpabuf_head(key_data), wpabuf_len(key_data),
saved->cipher, nik, &found_cipher_ver,
&found_lifetime, &lifetime_bitmap) != 0) {
wpa_printf(MSG_INFO,
"NAN: NIK KDE missing in decrypted key data");
goto out;
}
/* Lifetime KDE is optional; if present, its bitmap must mark NIK. */
if (found_lifetime &&
!(lifetime_bitmap & NAN_PASN_KEY_LIFETIME_NIK_BIT)) {
wpa_printf(MSG_INFO,
"NAN: Unexpected key bitmap in Key Lifetime KDE: 0x%04x",
lifetime_bitmap);
goto out;
}
if (cipher_ver) {
*cipher_ver = found_cipher_ver;
}
if (lifetime_sec) {
*lifetime_sec = found_lifetime;
}
wpa_hexdump_key(MSG_DEBUG, "NAN: Peer NIK from follow-up", nik,
NAN_PASN_NIK_LEN);
ret = 0;
out:
wpabuf_clear_free(key_data);
return ret;
}
static int nan_chan_to_freq_mhz(uint8_t chan)
{
if (chan >= 1 && chan <= 13) {
@@ -520,10 +1020,15 @@ void nan_pasn_initialize(struct nan_pasn_data *nan, const u8 *addr, int freq, bo
pasn->cipher = WPA_CIPHER_CCMP;
pasn_enable_kdk_derivation(pasn);
if (!derive_kek) {
pasn->derive_kek = false;
pasn->kek_len = 0;
}
/*
* NAN PASN uses kek_len 0 for in-frame PASN-PTK; NAN Management KEK comes
* from KDK via nan_crypto_derive_kek in nan_pasn_copy_keys_from_pasn.
* ND-PMK is filled by pasn_nd_pmk_derive_from_kdk_store (hostap
* nan_crypto_derive_nd_pmk_from_kdk). Matches hostap nan_pairing.c.
*/
(void)derive_kek;
pasn->derive_kek = false;
pasn->kek_len = 0;
if (nan->dev_sae_pin_len > 0) {
pasn->akmp = WPA_KEY_MGMT_SAE;
@@ -742,14 +1247,14 @@ static int nan_handle_pasn_auth(struct nan_pasn_data *nan,
return -1;
}
nan_pasn_auth_timeout_cancel(nan);
nan_pasn_post_pasn_pairing_confirm_evt(
nan, pasn, le_to_host16(mgmt->auth.status_code),
mgmt->sa, nan->cfg->dev_addr, false);
#ifdef CONFIG_TESTING_OPTIONS
nan_pasn_store_ptk(nan, &pasn->ptk);
#endif /* CONFIG_TESTING_OPTIONS */
nan_pasn_copy_keys_from_pasn(nan, pasn);
nan_pasn_install_nan_pairwise_tk(pasn);
if (nan_pasn_install_nan_pairwise_tk(nan, pasn) == 0 &&
nan->pairing_key_installed_cb) {
nan->pairing_key_installed_cb(pasn->peer_addr);
}
forced_memzero(pasn_get_ptk(pasn), sizeof(pasn->ptk));
nan_pasn_data_deinit(nan);
}
@@ -790,12 +1295,17 @@ int nan_pasn_auth_rx(struct nan_pasn_data *nan, const struct ieee80211_auth *mgm
if (ret < 0) {
wpa_printf(MSG_INFO, "PASN: wpa_pasn_auth_rx() failed");
nan->dev_role = NAN_ROLE_IDLE;
} else {
nan_pasn_post_pasn_pairing_confirm_evt(
nan, pasn, le_to_host16(mgmt->auth.status_code),
pasn->own_addr, pasn->peer_addr, true);
} else if (ret == 0 && pasn->status == WLAN_STATUS_SUCCESS) {
/*
* Pairing setup confirm maps to PASN completion. For initiator,
* this is only after Auth2 has been validated and Auth3 has been
* successfully built/transmitted by wpa_pasn_auth_rx().
*/
nan_pasn_copy_keys_from_pasn(nan, pasn);
nan_pasn_install_nan_pairwise_tk(pasn);
if (nan_pasn_install_nan_pairwise_tk(nan, pasn) == 0 &&
nan->pairing_key_installed_cb) {
nan->pairing_key_installed_cb(pasn->peer_addr);
}
}
#ifdef CONFIG_TESTING_OPTIONS
nan_pasn_store_ptk(nan, &pasn->ptk);
@@ -1033,6 +1543,7 @@ int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, i
struct nan_pasn_eloop_ctx {
uint8_t peer_addr[ETH_ALEN];
uint32_t pincode;
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb;
};
static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data)
@@ -1061,6 +1572,7 @@ static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data)
}
esp_nan_app_set_pasn_data(pd);
pd->pairing_key_installed_cb = ctx->pairing_key_installed_cb;
if (ctx->pincode != UINT32_MAX) {
n = os_snprintf(pin_digits, sizeof(pin_digits), "%06u",
@@ -1090,21 +1602,19 @@ static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data)
os_free(ctx);
}
int nan_pasn_auth_eloop(const uint8_t *peer_addr, uint32_t pincode)
int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode,
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb)
{
struct nan_pasn_eloop_ctx *ctx;
if (!peer_addr) {
return -1;
}
ctx = os_zalloc(sizeof(*ctx));
if (!ctx) {
if (!ctx || !peer_nmi) {
return -1;
}
os_memcpy(ctx->peer_addr, peer_addr, ETH_ALEN);
os_memcpy(ctx->peer_addr, peer_nmi, ETH_ALEN);
ctx->pincode = pincode;
ctx->pairing_key_installed_cb = pairing_key_installed_cb;
if (eloop_register_timeout(0, 0, nan_pasn_auth_eloop_cb, NULL, ctx) != 0) {
os_free(ctx);
@@ -1255,36 +1765,41 @@ fail:
struct pasn_responder_eloop_ctx {
uint8_t peer_addr[ETH_ALEN];
unsigned int has_peer;
uint32_t pincode;
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb;
};
static void pasn_responder_init_eloop_cb(void *eloop_ctx, void *user_data)
{
struct pasn_responder_eloop_ctx *ctx = user_data;
struct nan_pasn_data *pd;
(void)eloop_ctx;
if (!ctx) {
return;
}
pasn_responder_init(ctx->has_peer ? ctx->peer_addr : NULL, ctx->pincode);
if (pasn_responder_init(ctx->peer_addr, ctx->pincode) == 0) {
pd = esp_nan_app_get_pasn_data();
if (pd) {
pd->pairing_key_installed_cb = ctx->pairing_key_installed_cb;
}
}
os_free(ctx);
}
int pasn_responder_init_eloop(const uint8_t *peer_addr, uint32_t pincode)
int esp_nan_supp_pasn_responder_init(const uint8_t *peer_nmi, uint32_t pincode,
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb)
{
struct pasn_responder_eloop_ctx *ctx;
ctx = os_zalloc(sizeof(*ctx));
if (!ctx) {
if (!ctx || !peer_nmi) {
return -1;
}
ctx->pincode = pincode;
if (peer_addr) {
ctx->has_peer = 1;
os_memcpy(ctx->peer_addr, peer_addr, ETH_ALEN);
}
os_memcpy(ctx->peer_addr, peer_nmi, ETH_ALEN);
ctx->pairing_key_installed_cb = pairing_key_installed_cb;
if (eloop_register_timeout(0, 0, pasn_responder_init_eloop_cb, NULL, ctx) != 0) {
os_free(ctx);
@@ -1389,6 +1904,24 @@ int pasn_responder_init_eloop(const uint8_t *peer_addr, uint32_t pincode)
return -1;
}
int esp_nan_supp_pasn_responder_init(const uint8_t *peer_nmi, uint32_t pincode,
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb)
{
(void)peer_nmi;
(void)pincode;
(void)pairing_key_installed_cb;
return -1;
}
int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode,
esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb)
{
(void)peer_nmi;
(void)pincode;
(void)pairing_key_installed_cb;
return -1;
}
const struct nan_pasn_key_material *nan_pasn_get_saved_keys(void)
{
return NULL;
@@ -1398,4 +1931,19 @@ void nan_pasn_clear_saved_keys(void)
{
}
int nan_pasn_followup_decrypt_keys(const uint8_t *shared_key_attr,
size_t attr_total_len,
uint8_t *nik, size_t nik_size,
uint8_t *cipher_ver,
uint32_t *lifetime_sec)
{
(void)shared_key_attr;
(void)attr_total_len;
(void)nik;
(void)nik_size;
(void)cipher_ver;
(void)lifetime_sec;
return -1;
}
#endif /* CONFIG_ESP_WIFI_PASN_SUPPORT */
@@ -234,6 +234,12 @@ enum key_flag {
KEY_FLAG_PMK = BIT(6),
};
typedef enum {
NAN_KEY_ND_TK = 0,
NAN_KEY_ND_GTK,
NAN_KEY_NM_TK,
} nan_key_type_t;
typedef wifi_scan_channel_bitmap_t channel_bitmap_t;
uint8_t *esp_wifi_ap_get_prof_pmk_internal(void);
@@ -320,12 +320,9 @@ static int rsn_selector_to_bitfield(const u8 *s)
return WPA_CIPHER_BIP_GMAC_256;
#endif
#endif /* CONFIG_IEEE80211W */
if (RSN_SELECTOR_GET(s) == RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED){
printf("### function = %s line = %di ###\n",__func__,__LINE__);
if (RSN_SELECTOR_GET(s) == RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED)
return WPA_CIPHER_GTK_NOT_USED;
}
printf("### function = %s line = %di ###\n",__func__,__LINE__);
return 0;
}
@@ -2079,7 +2076,6 @@ int wpa_pasn_validate_rsne(const struct wpa_ie_data *data)
wpa_printf(MSG_DEBUG, "PASN: Invalid group data cipher");
return -1;
}
printf("### function = %s line = %d %d %d ###\n",__func__,__LINE__,data->has_pairwise,data->pairwise_cipher);
if (!data->has_pairwise || !data->pairwise_cipher ||
(data->pairwise_cipher & (data->pairwise_cipher - 1))) {
@@ -2353,11 +2349,76 @@ void pasn_nd_pmk_global_clear(void)
/**
* pasn_pmk_to_ptk - Calculate PASN PTK from PMK, addresses, etc.
* pasn_nd_pmk_derive_from_kdk_store - NAN ND-PMK from NM-KDK (hostap equivalent)
*
* Mirrors hostap nan_crypto_derive_nd_pmk_from_kdk() / nan_crypto_derive_from_kdk():
*
* ND-PMK = KDF-HASH-NNN(KDK, "NDP PMK Derivation",
* Pairing Initiator NMI || Pairing Responder NMI)
*
* NNN follows the NAN PASN cipher suite: SHA-256 for 128-bit suite (e.g. CCMP),
* SHA-384 for 256-bit suite (GCMP-256). Output is always PMK_LEN octets.
*/
int pasn_nd_pmk_derive_from_kdk_store(const u8 *kdk, size_t kdk_len,
int pairwise_cipher,
const u8 *initiator_nmi,
const u8 *responder_nmi)
{
static const char label[] = "NDP PMK Derivation";
u8 data[2 * ETH_ALEN];
int ret;
if (!kdk || !kdk_len || !initiator_nmi || !responder_nmi)
return -1;
os_memcpy(data, initiator_nmi, ETH_ALEN);
os_memcpy(data + ETH_ALEN, responder_nmi, ETH_ALEN);
wpa_printf(MSG_DEBUG, "PASN: Deriving ND-PMK from NM-KDK (NAN pairing)");
wpa_hexdump_key(MSG_DEBUG, "PASN: KDK", kdk, kdk_len);
wpa_printf(MSG_DEBUG, "PASN: Initiator NMI " MACSTR,
MAC2STR(initiator_nmi));
wpa_printf(MSG_DEBUG, "PASN: Responder NMI " MACSTR,
MAC2STR(responder_nmi));
if (pairwise_cipher == WPA_CIPHER_GCMP_256) {
#ifdef CONFIG_SHA384
wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA384");
ret = sha384_prf(kdk, kdk_len, label, data, sizeof(data),
pasn_nd_pmk_global.nd_pmk, PMK_LEN);
#else /* CONFIG_SHA384 */
wpa_printf(MSG_DEBUG, "PASN: ND-PMK SHA384 not supported");
return -1;
#endif /* CONFIG_SHA384 */
} else {
wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA256");
ret = sha256_prf(kdk, kdk_len, label, data, sizeof(data),
pasn_nd_pmk_global.nd_pmk, PMK_LEN);
}
if (ret < 0)
return -1;
pasn_nd_pmk_global.valid = 1;
wpa_hexdump_key(MSG_DEBUG, "PASN: ND-PMK (global):",
pasn_nd_pmk_global.nd_pmk, PMK_LEN);
return 0;
}
/**
* pasn_pmk_to_ptk - Calculate PASN/EPPKE PTK from PMK, addresses, etc.
* @pmk: Pairwise master key
* @pmk_len: Length of PMK
* @spa: Suppplicant address
* @bssid: AP BSSID
* @spa: For EPPKE authentication, non-AP MLD MAC address is used for MLO. For
* PASN authentication or EPPKE authentication for non-MLO, non-AP STA link
* MAC address is used.
* @bssid: For EPPKE authentication, AP MLD MAC address is used for MLO. For
* PASN authentication or EPPKE authentication for non-MLO, AP BSSID is
* used.
* @dhss: Is the shared secret (DHss) derived from the PASN ephemeral key
* exchange encoded as an octet string
* @dhss_len: The length of dhss in octets
@@ -2367,29 +2428,24 @@ void pasn_nd_pmk_global_clear(void)
* @kdk_len: the length in octets that should be derived for HTLK. Can be zero.
* @kek_len: The length in octets that should be derived for KEK. Can be zero.
* @alg: Output variable for indicating the selected hash algorithm
* @is_eppke: EPPKE authentication
* Returns: 0 on success, -1 on failure
*/
int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
const u8 *spa, const u8 *bssid,
const u8 *dhss, size_t dhss_len,
struct wpa_ptk *ptk, int akmp, int cipher,
size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg)
size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg,
bool is_eppke)
{
u8 tmp[WPA_KCK_MAX_LEN + WPA_KEK_MAX_LEN + WPA_TK_MAX_LEN +
WPA_KDK_MAX_LEN];
u8 kek_buf[WPA_KEK_MAX_LEN];
u8 nd_pmk_buf[PMK_LEN];
const u8 *pos;
u8 *data;
size_t data_len, ptk_len;
size_t first_prf_len;
const size_t nan_mgmt_kek_len = 16;
int ret = -1;
const char *label = "PASN PTK Derivation";
const char *kek_label = "NAN Management KEK Derivation";
const char *nd_pmk_label = "NDP PMK Derivation";
(void) kek_len;
const char *label = is_eppke ? "EPPKE PTK Derivation" :
"PASN PTK Derivation";
if (!pmk || !pmk_len) {
wpa_printf(MSG_ERROR, "PASN: No PMK set for PTK derivation");
@@ -2401,10 +2457,14 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
return -1;
}
pasn_nd_pmk_global_clear();
/*
* PASN-PTK = KDF(PMK, “PASN PTK Derivation”, SPA || BSSID || DHss)
* Use "EPPKE PTK Derivation" instead of "PASN PTK Derivation" for
* EPPKE Authentication per IEEE P802.11bi/D4.0, 12.16.9.3.4 (PTKSA
* derivation and MIC computation with EPPKE authentication). For EPPKE
* MLO, the non-AP MLD MAC address is used instead of the SPA and the
* AP MLD MAC address instead of the BSSID.
*
* PASN-PTK = KDF(PMK, "PASN PTK Derivation", SPA || BSSID || DHss)
*
* KCK = L(PASN-PTK, 0, 256)
* TK = L(PASN-PTK, 256, TK_bits)
@@ -2419,11 +2479,10 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
os_memcpy(data + ETH_ALEN, bssid, ETH_ALEN);
os_memcpy(data + 2 * ETH_ALEN, dhss, dhss_len);
/* KEK is not taken from the first PASN-PTK layout; optional NAN KEK below. */
ptk->kck_len = WPA_PASN_KCK_LEN;
ptk->tk_len = wpa_cipher_key_len(cipher);
ptk->kdk_len = kdk_len;
ptk->kek_len = 0;
ptk->kek_len = kek_len;
ptk->kek2_len = 0;
ptk->kck2_len = 0;
@@ -2434,14 +2493,16 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
goto err;
}
first_prf_len = ptk->kck_len + ptk->tk_len + ptk->kdk_len;
if (first_prf_len > sizeof(tmp))
ptk_len = ptk->kck_len + ptk->tk_len + ptk->kdk_len + ptk->kek_len;
if (ptk_len > sizeof(tmp))
goto err;
ptk_len = first_prf_len;
*alg = pasn_select_hash_alg(akmp, cipher, pmk_len);
switch (*alg) {
case RSN_HASH_SHA512:
wpa_printf(MSG_DEBUG, "PASN: SHA512 PTK derivation not supported");
goto err;
case RSN_HASH_SHA384:
#ifdef CONFIG_SHA384
wpa_printf(MSG_DEBUG, "PASN: PTK derivation using SHA384");
@@ -2450,7 +2511,7 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
ptk_len) < 0)
goto err;
break;
#endif
#endif /* CONFIG_SHA384 */
case RSN_HASH_SHA256:
wpa_printf(MSG_DEBUG, "PASN: PTK derivation using SHA256");
@@ -2476,6 +2537,13 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
wpa_hexdump_key(MSG_DEBUG, "PASN: KCK:", ptk->kck, WPA_PASN_KCK_LEN);
pos = &tmp[WPA_PASN_KCK_LEN];
if (kek_len) {
os_memcpy(ptk->kek, pos, kek_len);
wpa_hexdump_key(MSG_DEBUG, "PASN: KEK:",
ptk->kek, ptk->kek_len);
pos += kek_len;
}
os_memcpy(ptk->tk, pos, ptk->tk_len);
wpa_hexdump_key(MSG_DEBUG, "PASN: TK:", ptk->tk, ptk->tk_len);
pos += ptk->tk_len;
@@ -2484,78 +2552,12 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
os_memcpy(ptk->kdk, pos, ptk->kdk_len);
wpa_hexdump_key(MSG_DEBUG, "PASN: KDK:",
ptk->kdk, ptk->kdk_len);
/*
* NAN Management KEK = KDF(KDK, "NAN Management KEK Derivation",
* SPA || BSSID || DHss)
*/
switch (*alg) {
case RSN_HASH_SHA384:
#ifdef CONFIG_SHA384
wpa_printf(MSG_DEBUG, "PASN: KEK derivation using SHA384");
if (sha384_prf(ptk->kdk, ptk->kdk_len, kek_label, data,
data_len, kek_buf, nan_mgmt_kek_len) < 0)
goto err;
break;
#endif
case RSN_HASH_SHA256:
wpa_printf(MSG_DEBUG, "PASN: KEK derivation using SHA256");
if (sha256_prf(ptk->kdk, ptk->kdk_len, kek_label, data,
data_len, kek_buf, nan_mgmt_kek_len) < 0)
goto err;
break;
default:
wpa_printf(MSG_DEBUG, "PASN: Unsupported hash algorithm %d",
*alg);
goto err;
}
os_memcpy(ptk->kek, kek_buf, nan_mgmt_kek_len);
ptk->kek_len = nan_mgmt_kek_len;
wpa_hexdump_key(MSG_DEBUG, "PASN: KEK (NAN management):",
ptk->kek, ptk->kek_len);
/*
* ND-PMK = KDF(KDK, "NDP PMK Derivation", SPA || BSSID || DHss)
*/
switch (*alg) {
case RSN_HASH_SHA384:
#ifdef CONFIG_SHA384
wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA384");
if (sha384_prf(ptk->kdk, ptk->kdk_len, nd_pmk_label, data,
data_len, nd_pmk_buf, PMK_LEN) < 0)
goto err;
break;
#endif
case RSN_HASH_SHA256:
wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA256");
if (sha256_prf(ptk->kdk, ptk->kdk_len, nd_pmk_label, data,
data_len, nd_pmk_buf, PMK_LEN) < 0)
goto err;
break;
default:
wpa_printf(MSG_DEBUG, "PASN: Unsupported hash algorithm %d",
*alg);
goto err;
}
os_memcpy(pasn_nd_pmk_global.nd_pmk, nd_pmk_buf, PMK_LEN);
pasn_nd_pmk_global.valid = 1;
wpa_hexdump_key(MSG_DEBUG, "PASN: ND-PMK (global):",
pasn_nd_pmk_global.nd_pmk, PMK_LEN);
forced_memzero(nd_pmk_buf, sizeof(nd_pmk_buf));
}
ptk->ptk_len = ptk->kck_len + ptk->kek_len + ptk->tk_len + ptk->kdk_len;
ptk->ptk_len = ptk_len;
forced_memzero(tmp, sizeof(tmp));
ret = 0;
err:
forced_memzero(kek_buf, sizeof(kek_buf));
forced_memzero(nd_pmk_buf, sizeof(nd_pmk_buf));
bin_clear_free(data, data_len);
return ret;
}
@@ -2568,6 +2570,8 @@ err:
size_t pasn_mic_len(enum rsn_hash_alg alg)
{
switch (alg) {
case RSN_HASH_SHA512:
return 32;
case RSN_HASH_SHA384:
return 24;
case RSN_HASH_SHA256:
@@ -28,7 +28,9 @@
#define WPA_PASN_MAX_MIC_LEN 32
/**
* NDP PMK (32 octets) from KDK in pasn_pmk_to_ptk (label "NDP PMK Derivation").
* ND-PMK (32 octets) for Wi-Fi NAN pairing, derived from NM-KDK via
* pasn_nd_pmk_derive_from_kdk_store() (same KDF as hostap
* nan_crypto_derive_nd_pmk_from_kdk).
* @valid: nonzero after successful derivation in the current session.
*/
struct pasn_nd_pmk_store {
@@ -40,6 +42,11 @@ extern struct pasn_nd_pmk_store pasn_nd_pmk_global;
void pasn_nd_pmk_global_clear(void);
int pasn_nd_pmk_derive_from_kdk_store(const u8 *kdk, size_t kdk_len,
int pairwise_cipher,
const u8 *initiator_nmi,
const u8 *responder_nmi);
#define COMEBACK_PENDING_IDX_SIZE 256
enum rsn_hash_alg {
@@ -598,7 +605,8 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len,
const u8 *spa, const u8 *bssid,
const u8 *dhss, size_t dhss_len,
struct wpa_ptk *ptk, int akmp, int cipher,
size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg);
size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg,
bool is_eppke);
size_t pasn_mic_len(enum rsn_hash_alg alg);
@@ -1364,16 +1364,29 @@ int wpa_pasn_auth_rx(struct pasn_data *pasn, const u8 *data, size_t len,
goto fail;
}
pasn_nd_pmk_global_clear();
ret = pasn_pmk_to_ptk(pasn->pmk, pasn->pmk_len,
pasn->own_addr, pasn->peer_addr,
wpabuf_head(secret), wpabuf_len(secret),
&pasn->ptk, pasn->akmp, pasn->cipher,
pasn->kdk_len, pasn->kek_len, &pasn->hash_alg);
pasn->kdk_len, pasn->kek_len, &pasn->hash_alg,
false);
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: Failed to derive PTK");
goto fail;
}
if (pasn->ptk.kdk_len) {
ret = pasn_nd_pmk_derive_from_kdk_store(
pasn->ptk.kdk, pasn->ptk.kdk_len, pasn->cipher,
pasn->own_addr, pasn->peer_addr);
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: Failed to derive ND-PMK");
goto fail;
}
}
if (pasn->secure_ltf) {
ret = wpa_ltf_keyseed(&pasn->ptk, pasn->akmp, pasn->cipher);
if (ret) {
@@ -413,16 +413,28 @@ pasn_derive_keys(struct pasn_data *pasn,
pasn->pmk_len = pmk_len;
os_memcpy(pasn->pmk, pmk, pmk_len);
pasn_nd_pmk_global_clear();
ret = pasn_pmk_to_ptk(pmk, pmk_len, peer_addr, own_addr,
wpabuf_head(secret), wpabuf_len(secret),
&pasn->ptk, pasn->akmp,
pasn->cipher, pasn->kdk_len, pasn->kek_len,
&pasn->hash_alg);
&pasn->hash_alg, false);
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: Failed to derive PTK");
return -1;
}
if (pasn->ptk.kdk_len) {
ret = pasn_nd_pmk_derive_from_kdk_store(
pasn->ptk.kdk, pasn->ptk.kdk_len, pasn->cipher,
peer_addr, own_addr);
if (ret) {
wpa_printf(MSG_DEBUG, "PASN: Failed to derive ND-PMK");
return -1;
}
}
if (pasn->secure_ltf) {
ret = wpa_ltf_keyseed(&pasn->ptk, pasn->akmp,
pasn->cipher);