feat(wifi): add NAN group data and management frame protection

Add Wi-Fi Aware group-key support to secured NDPs so group-addressed
traffic can be protected, for interop with iOS/macOS peers:

- GTK (NCS-GTK-CCM-128) protects group-addressed data.
- IGTK/BIGTK (BIP) protect group management traffic - multicast SDFs,
  Beacons.

Capabilities are advertised in the CSIA IE: group_data_prot maps to GTKSA,
group_mgmt_prot to IGTKSA/BIGTKSA. The CSIA cannot encode IGTK/BIGTK
without GTK (WiFi Aware spec 9.5.21.2, Table 122), so enabling group_mgmt_prot
forces group_data_prot on for every secured service.

Expose per-service group_data_prot and device-global group_mgmt_prot.
This commit is contained in:
Sarvesh Bodakhe
2026-07-04 00:05:20 +08:00
committed by BOT
parent cd16349be9
commit 310b3d6571
11 changed files with 1052 additions and 89 deletions
+22 -8
View File
@@ -89,10 +89,11 @@ typedef struct {
uint8_t num_pmkids; /**< Number of parsed PMKIDs */ 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 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_data_prot: 1; /**< Peer advertises group data frame protection */
uint8_t group_mgmt_prot: 1; /**< Peer advertises group mgmt frame protection */ uint8_t group_mgmt_prot: 1; /**< Peer advertises IGTKSA (CSIA caps bits 1-2 != 0) */
uint8_t pairing_setup: 1; /**< Pairing setup: 0 - disabled, 1 - enabled */ uint8_t pairing_setup: 1; /**< Pairing setup: 0 - disabled, 1 - enabled */
uint8_t npk_nik_caching: 1; /**< NPK/NIK caching: 0 - disabled, 1 - enabled (valid if pairing_setup) */ uint8_t npk_nik_caching: 1; /**< NPK/NIK caching: 0 - disabled, 1 - enabled (valid if pairing_setup) */
uint8_t reserved: 4; /**< Reserved */ uint8_t group_bigtk_prot: 1; /**< Peer advertises BIGTKSA (CSIA caps bits 1-2 == 10) */
uint8_t reserved: 3; /**< Reserved */
} wifi_nan_peer_sdf_security_t; } wifi_nan_peer_sdf_security_t;
/* NAN Peer info parsed from SDF */ /* NAN Peer info parsed from SDF */
@@ -230,9 +231,11 @@ struct nan_secure_dp_funcs {
* with the given Key Data field length. */ * with the given Key Data field length. */
uint32_t (*get_shared_key_desc_attr_len)(uint16_t key_data_len); uint32_t (*get_shared_key_desc_attr_len)(uint16_t key_data_len);
/* Byte length of the M4 Shared Key Descriptor including any /* Exact byte length of the M4 (NDP Security Install) Shared Key Descriptor
* encrypted KDE payload (GTK/IGTK/BIGTK). */ * attribute for this NDP, including any encrypted KDE payload (GTK/IGTK/BIGTK).
int (*ndp_security_install_get_shared_desc_len)(void); * @ndp_id + @peer_nmi identify the NDL so the host returns the exact length the
* builder will write (no over-reservation / on-air zero-padding). */
int (*ndp_security_install_get_shared_desc_len)(uint8_t ndp_id, const uint8_t *peer_nmi);
uint8_t (*get_ndp_resp_num_pmkids)(uint8_t ndp_id, const uint8_t *peer_nmi); uint8_t (*get_ndp_resp_num_pmkids)(uint8_t ndp_id, const uint8_t *peer_nmi);
uint32_t (*get_ndp_resp_shared_key_desc_len)(uint8_t ndp_id, const uint8_t *peer_nmi); uint32_t (*get_ndp_resp_shared_key_desc_len)(uint8_t ndp_id, const uint8_t *peer_nmi);
@@ -362,10 +365,21 @@ struct nan_secure_dp_funcs {
const wifi_nan_discovery_security_params_t *sec_cfg, const wifi_nan_discovery_security_params_t *sec_cfg,
wifi_nan_security_params_t *out_derived); wifi_nan_security_params_t *out_derived);
/* Returns the cipher-suite bitmap negotiated for an in-flight NDP, /* Returns the full cipher-suite bitmap negotiated for an in-flight NDP
* or 0 if the NDP is open. Used by RX/TX paths to decide whether * (including the group cipher NCS-GTK when group-addressed data protection
* to apply CCMP/GCMP and which key length to use. */ * is in use), or 0 if the NDP is open. The blob treats this as an opaque
* value passed straight to the host CSIA callbacks (construct_csia /
* get_csia_len) to build the on-air M1/M3 CSIA own-bitmap; it must NOT
* interpret individual CSID bits. Pairwise cipher selection and key install
* are host-driven and independent of this value. */
uint16_t (*get_ndp_security_csid)(uint8_t ndp_id, const uint8_t *peer_nmi); uint16_t (*get_ndp_security_csid)(uint8_t ndp_id, const uint8_t *peer_nmi);
/* Exact byte length of the M3 (NDP Confirm) Shared Key Descriptor attribute for
* this NDP, including any encrypted KDE payload (GTK/IGTK/BIGTK). @ndp_id +
* @peer_nmi identify the NDL. Mirrors ndp_security_install_get_shared_desc_len
* for the initiator path. Appended at the end of the struct to preserve the
* layout of the fields above. */
int (*ndp_confirm_get_shared_desc_len)(uint8_t ndp_id, const uint8_t *peer_nmi);
}; };
/** /**
@@ -606,6 +606,7 @@ typedef struct {
bool disable_random_mac;/**< Disable the MAC Randomisation in NAN */ bool disable_random_mac;/**< Disable the MAC Randomisation in NAN */
bool reset_current_nvs_creds; /**< Erase all NAN credentials (own NIK and cached peer NIK/NPK entries) saved in NVS before starting. */ bool reset_current_nvs_creds; /**< Erase all NAN credentials (own NIK and cached peer NIK/NPK entries) saved in NVS before starting. */
bool use_nvs_for_caching; /**< Persist newly-learned peer credentials (NIK/NPK) to NVS so they survive across reboots. */ bool use_nvs_for_caching; /**< Persist newly-learned peer credentials (NIK/NPK) to NVS so they survive across reboots. */
bool group_mgmt_prot; /**< Device-global group management protection (IGTKSA/BIGTKSA): BIP-protect Beacons + multicast SDFs. Forces GTKSA on all secured services (CSIA caps cannot encode IGTK/BIGTK without GTKSA). */
} wifi_nan_sync_config_t; } wifi_nan_sync_config_t;
/** /**
@@ -264,13 +264,27 @@ static void nan_app_clear_one_peer_tks(const uint8_t *peer_nmi)
key_rsc, sizeof(key_rsc), key_rsc, sizeof(key_rsc),
NULL, 0, NAN_KEY_ND_TK); NULL, 0, NAN_KEY_ND_TK);
/* Drop the peer's RX GTK (bound to the peer NDI) and wipe local GTK
* state. The TX GTK keyed on the local NDI is released by the blob
* when the NDI/interface is torn down. */
esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_CCMP,
key_addr, ndl->gtk_keyid, 0,
key_rsc, sizeof(key_rsc),
NULL, 0, NAN_KEY_ND_GTK);
forced_memzero(ndl->nd_tk, sizeof(ndl->nd_tk)); forced_memzero(ndl->nd_tk, sizeof(ndl->nd_tk));
forced_memzero(ndl->nd_kck, sizeof(ndl->nd_kck)); forced_memzero(ndl->nd_kck, sizeof(ndl->nd_kck));
forced_memzero(ndl->nd_kek, sizeof(ndl->nd_kek)); forced_memzero(ndl->nd_kek, sizeof(ndl->nd_kek));
forced_memzero(ndl->gtk, sizeof(ndl->gtk));
forced_memzero(ndl->own_gtk, sizeof(ndl->own_gtk));
ndl->ptk_set = 0; ndl->ptk_set = 0;
ndl->tk_len = 0; ndl->tk_len = 0;
ndl->kck_len = 0; ndl->kck_len = 0;
ndl->kek_len = 0; ndl->kek_len = 0;
ndl->gtk_set = 0;
ndl->own_gtk_set = 0;
ndl->gtk_len = 0;
ndl->own_gtk_len = 0;
} }
/* Fallback when no NDL slot tracks peer_ndi yet. */ /* Fallback when no NDL slot tracks peer_ndi yet. */
@@ -601,6 +615,18 @@ static struct own_svc_info *nan_claim_own_svc_slot(uint8_t type, const char svc_
forced_memzero(&p_svc->derived_security, sizeof(p_svc->derived_security)); forced_memzero(&p_svc->derived_security, sizeof(p_svc->derived_security));
if (security_cfg) { if (security_cfg) {
memcpy(&p_svc->user_cfg, security_cfg, sizeof(*security_cfg)); memcpy(&p_svc->user_cfg, security_cfg, sizeof(*security_cfg));
/* Device-global group_mgmt_prot (IGTKSA/BIGTKSA) forces GTKSA on every
* secured service: the CSIA capability field has no "IGTK/BIGTK without
* GTKSA" encoding (§9.5.21.2 Table 122), so advertising group-management
* protection mandates advertising GTKSA. Warn and force it on if the app
* requested group_data_prot=0. Forcing support never blocks a peer that
* lacks protection — keys activate only after capability negotiation. */
if (s_nan_ctx.group_mgmt_prot && !p_svc->user_cfg.group_data_prot) {
ESP_LOGW(TAG, "group_data_prot forced ON for '%s': group_mgmt_prot is "
"enabled device-wide; GTKSA cannot be advertised without it",
svc_name);
p_svc->user_cfg.group_data_prot = 1;
}
} }
#ifdef CONFIG_ESP_WIFI_NAN_PAIRING #ifdef CONFIG_ESP_WIFI_NAN_PAIRING
if (pairing) { if (pairing) {
@@ -681,6 +707,7 @@ static void nan_record_new_ndl(uint8_t ndp_id, uint8_t publish_id, uint8_t peer_
if (ndl && reuse_slot) { if (ndl && reuse_slot) {
ndl->ndp_id = ndp_id; ndl->ndp_id = ndp_id;
ndl->own_role = own_role; ndl->own_role = own_role;
ndl->device_caps = device_caps;
return; return;
} }
if (ndl) { if (ndl) {
@@ -1024,7 +1051,7 @@ static void nan_app_replied_cb(uint8_t pub_id, struct nan_cb_peer_info *peer_inf
evt->subscribe_id = sub_id; evt->subscribe_id = sub_id;
MACADDR_COPY(evt->sub_if_mac, sub_nmi); MACADDR_COPY(evt->sub_if_mac, sub_nmi);
ESP_LOGI(TAG, "Sent Publish to Peer "MACSTR" [Peer Subscribe id - %d]", MAC2STR(sub_nmi), sub_id); //ESP_LOGI(TAG, "Sent Publish to Peer "MACSTR" [Peer Subscribe id - %d]", MAC2STR(sub_nmi), sub_id);
if (ssi && ssi_len) { if (ssi && ssi_len) {
memcpy(evt->ssi, ssi, ssi_len); memcpy(evt->ssi, ssi, ssi_len);
evt->ssi_len = ssi_len; evt->ssi_len = ssi_len;
@@ -1147,8 +1174,11 @@ static void nan_app_ndp_indication_cb(uint8_t pub_id, struct ndp_cb_peer_info *p
nan_record_new_ndl(ndp_id, pub_id, peer_nmi, ESP_WIFI_NDP_ROLE_RESPONDER, device_caps); nan_record_new_ndl(ndp_id, pub_id, peer_nmi, ESP_WIFI_NDP_ROLE_RESPONDER, device_caps);
if (!nan_find_peer_svc(pub_id, 0, peer_nmi)) { struct peer_svc_info *p_peer_svc = nan_find_peer_svc(pub_id, 0, peer_nmi);
if (!p_peer_svc) {
nan_record_peer_svc(pub_id, 0, peer_nmi, device_caps); nan_record_peer_svc(pub_id, 0, peer_nmi, device_caps);
} else {
p_peer_svc->device_caps = device_caps;
} }
struct ndl_info *ndl = nan_find_ndl(ndp_id, peer_nmi); struct ndl_info *ndl = nan_find_ndl(ndp_id, peer_nmi);
@@ -1161,6 +1191,17 @@ static void nan_app_ndp_indication_cb(uint8_t pub_id, struct ndp_cb_peer_info *p
nan_security_apply_pending(ndl, p_own_svc, pub_id, peer_nmi, peer_ndi); nan_security_apply_pending(ndl, p_own_svc, pub_id, peer_nmi, peer_ndi);
#endif #endif
if (device_caps & NAN_CAPS_NDPE_ATTR) {
uint8_t own_bssid[6];
esp_err_t err = esp_wifi_get_mac(WIFI_IF_NAN, own_bssid);
if (err != ESP_OK) {
NAN_DATA_UNLOCK();
ESP_LOGE(TAG, "Cannot get own BSSID!");
return;
}
esp_wifi_nan_get_ipv6_linklocal_from_mac(&own_ipv6.u_addr.ip6, own_bssid);
}
if (p_own_svc->ndp_resp_needed) { if (p_own_svc->ndp_resp_needed) {
ESP_LOGD(TAG, "NDP Req from "MACSTR" [NDP Id: %d], Accept OR Deny using NDP command", ESP_LOGD(TAG, "NDP Req from "MACSTR" [NDP Id: %d], Accept OR Deny using NDP command",
MAC2STR(peer_nmi), ndp_id); MAC2STR(peer_nmi), ndp_id);
@@ -1359,10 +1400,6 @@ static void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer
goto done; goto done;
} }
#ifndef NAN_KEY_ND_TK
#define NAN_KEY_ND_TK 0
#endif
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY #ifdef CONFIG_ESP_WIFI_NAN_SECURITY
if (ndl->security_ctx.type == WIFI_NAN_SECURITY_ENCRYPTED) { if (ndl->security_ctx.type == WIFI_NAN_SECURITY_ENCRYPTED) {
uint8_t key_rsc[8] = {0}; uint8_t key_rsc[8] = {0};
@@ -1375,12 +1412,98 @@ static void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer
ndl->nd_tk, ndl->nd_tk,
NAN_NCS_SK_128_TK_LEN, NAN_NCS_SK_128_TK_LEN,
NAN_KEY_ND_TK); NAN_KEY_ND_TK);
ESP_LOG_BUFFER_HEXDUMP("## ND-TK ", ndl->nd_tk, NAN_NCS_SK_128_TK_LEN, ESP_LOG_INFO);
ret = esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_CCMP,
peer_nmi,
0,
1,
key_rsc,
sizeof(key_rsc),
ndl->nd_tk,
NAN_NCS_SK_128_TK_LEN,
NAN_KEY_NM_TK);
if (ret != 0) { if (ret != 0) {
ESP_LOGE(TAG, "NDP confirm: failed to install NAN pairwise key (ndp_id=%d, ret=%d)", ndp_id, ret); ESP_LOGE(TAG, "NDP confirm: failed to install NAN pairwise key (ndp_id=%d, ret=%d)", ndp_id, ret);
os_free(evt); os_free(evt);
nan_ndp_confirm_teardown(peer_nmi, ndp_id); nan_ndp_confirm_teardown(peer_nmi, ndp_id);
goto done; goto done;
} }
/* Group keys (ND-GTK) exchanged during NDP setup (§7.1.3.2): our GTK
* is the TX key bound to the local NDI; the peer's GTK is the RX key
* bound to the peer NDI. Best-effort — a GTK install failure must not
* tear down the working unicast datapath. */
if (ndl->own_gtk_set && ndl->own_gtk_len) {
int gret = esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_CCMP,
own_ndi, ndl->own_gtk_keyid, 1,
ndl->own_gtk_rsc, NAN_KEY_RSC_LEN,
ndl->own_gtk, ndl->own_gtk_len,
NAN_KEY_ND_GTK);
if (gret != 0) {
ESP_LOGW(TAG, "NDP confirm: own GTK (TX) install failed (ndp_id=%d, ret=%d)", ndp_id, gret);
} else {
ESP_LOGI(TAG, "NDP confirm: own GTK (TX) installed (keyid=%d)", ndl->own_gtk_keyid);
}
ESP_LOG_BUFFER_HEXDUMP("## ND-GTK ", ndl->own_gtk, ndl->own_gtk_len, ESP_LOG_INFO);
}
if (ndl->gtk_set && ndl->gtk_len) {
int gret = esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_CCMP,
peer_ndi, ndl->gtk_keyid, 0,
ndl->gtk_rsc, NAN_KEY_RSC_LEN,
ndl->gtk, ndl->gtk_len,
NAN_KEY_ND_GTK);
if (gret != 0) {
ESP_LOGW(TAG, "NDP confirm: peer GTK (RX) install failed (ndp_id=%d, ret=%d)", ndp_id, gret);
} else {
ESP_LOGI(TAG, "NDP confirm: peer GTK (RX) installed (keyid=%d)", ndl->gtk_keyid);
}
}
/* Own IGTK/BIGTK (TX) are installed once at NAN start (see
* nan_security_install_own_group_integrity_keys); not re-installed here,
* to preserve the blob's monotonic BIPN/IPN across the session. Only the
* peer RX keys are bound at NDP confirm. §7.1.3.3/§7.1.3.4; NMI==NDI today.
* Peer IGTK/BIGTK install RX-only against the peer NMI. seq (IPN/BIPN)
* starts at 0 for now; thread the peer's KDE IPN/BIPN once the blob
* consumes it for the BIP replay counter. */
if (ndl->igtk_set && ndl->igtk_len) {
if (ndl->igtk_len != NAN_ND_GTK_LEN) {
ESP_LOGW(TAG, "NDP confirm: peer IGTK len=%d unsupported (BIP-CMAC-128 only); skipping",
ndl->igtk_len);
} else {
int r = esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_BIP_CMAC_128,
peer_nmi, ndl->igtk_keyid, 0,
key_rsc, 6,
ndl->igtk, ndl->igtk_len,
NAN_KEY_ND_IGTK);
if (r != 0) {
ESP_LOGW(TAG, "NDP confirm: peer IGTK (RX) install failed, rc=0x%x (ndp_id=%d)", r, ndp_id);
} else {
ESP_LOGI(TAG, "NDP confirm: peer IGTK (RX) installed (keyid=%d)", ndl->igtk_keyid);
}
/* Peer IGTK bytes for sniffer MIC cross-check vs the peer's multicast SDFs. */
ESP_LOG_BUFFER_HEXDUMP("## PEER ND-IGTK ", ndl->igtk, ndl->igtk_len, ESP_LOG_INFO);
}
}
if (ndl->bigtk_set && ndl->bigtk_len) {
if (ndl->bigtk_len != NAN_ND_GTK_LEN) {
ESP_LOGW(TAG, "NDP confirm: peer BIGTK len=%d unsupported (BIP-CMAC-128 only); skipping",
ndl->bigtk_len);
} else {
int r = esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_BIP_CMAC_128,
peer_nmi, ndl->bigtk_keyid, 0,
key_rsc, 6,
ndl->bigtk, ndl->bigtk_len,
NAN_KEY_ND_BIGTK);
if (r != 0) {
ESP_LOGW(TAG, "NDP confirm: peer BIGTK (RX) install failed, rc=0x%x (ndp_id=%d)", r, ndp_id);
} else {
ESP_LOGI(TAG, "NDP confirm: peer BIGTK (RX) installed (keyid=%d)", ndl->bigtk_keyid);
}
/* Peer BIGTK bytes for sniffer MIC cross-check vs the peer's protected Beacons. */
ESP_LOG_BUFFER_HEXDUMP("## PEER ND-BIGTK ", ndl->bigtk, ndl->bigtk_len, ESP_LOG_INFO);
}
}
} }
#endif /* CONFIG_ESP_WIFI_NAN_SECURITY */ #endif /* CONFIG_ESP_WIFI_NAN_SECURITY */
evt->status = status; evt->status = status;
@@ -1506,6 +1629,7 @@ static struct nan_secure_dp_funcs s_nan_secure_dp_funcs = {
.ndp_security_install_get_shared_desc_len = esp_nan_ndp_security_install_get_shared_desc_len, .ndp_security_install_get_shared_desc_len = esp_nan_ndp_security_install_get_shared_desc_len,
.get_ndp_resp_num_pmkids = esp_nan_get_ndp_resp_num_pmkids, .get_ndp_resp_num_pmkids = esp_nan_get_ndp_resp_num_pmkids,
.get_ndp_resp_shared_key_desc_len = esp_nan_get_ndp_resp_shared_key_desc_len, .get_ndp_resp_shared_key_desc_len = esp_nan_get_ndp_resp_shared_key_desc_len,
.ndp_confirm_get_shared_desc_len = esp_nan_ndp_confirm_get_shared_desc_len,
/* CSIA / SCIA construction */ /* CSIA / SCIA construction */
.construct_csia = esp_nan_construct_csia, .construct_csia = esp_nan_construct_csia,
@@ -1686,6 +1810,24 @@ void esp_nan_action_start(esp_netif_t *nan_netif)
}; };
esp_nan_internal_register_callbacks(&nan_cb); esp_nan_internal_register_callbacks(&nan_cb);
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
/* Device-global group-management protection (IGTKSA/BIGTKSA), one per NMI,
* sourced from the NAN start config (wifi_nan_sync_config_t.group_mgmt_prot).
* The blob stores it at nan_start; we read it back here. Default on if the
* config can't be read, preserving protected-by-default behavior. */
{
wifi_config_t nan_cfg = {0};
s_nan_ctx.group_mgmt_prot =
(esp_wifi_get_config(WIFI_IF_NAN, &nan_cfg) == ESP_OK)
? nan_cfg.nan.group_mgmt_prot : true;
}
/* Install the device-global IGTK/BIGTK for TX now (when enabled) so Beacons
* (BIGTK) and group-addressed SDFs (IGTK) are BIP-protected from the first
* frame, like iOS. The blob gates beacon BIP-TX on an active BIGTK index
* only (no NDP state), so installing here is sufficient. */
nan_security_install_own_group_integrity_keys();
#endif
ESP_LOGI(TAG, "NAN Discovery started."); ESP_LOGI(TAG, "NAN Discovery started.");
os_event_group_clear_bits(nan_event_group, NAN_STOPPED_BIT); os_event_group_clear_bits(nan_event_group, NAN_STOPPED_BIT);
os_event_group_set_bits(nan_event_group, NAN_STARTED_BIT); os_event_group_set_bits(nan_event_group, NAN_STARTED_BIT);
@@ -2525,7 +2667,6 @@ esp_err_t esp_wifi_nan_datapath_resp(wifi_nan_datapath_resp_t *resp)
ESP_LOGE(TAG, "Need NDP Indication before NDP Response can be sent"); ESP_LOGE(TAG, "Need NDP Indication before NDP Response can be sent");
goto fail; goto fail;
} }
if (MACADDR_EQUAL(resp->peer_mac, null_mac)) { if (MACADDR_EQUAL(resp->peer_mac, null_mac)) {
MACADDR_COPY(resp->peer_mac, ndl->peer_nmi); MACADDR_COPY(resp->peer_mac, ndl->peer_nmi);
} }
@@ -130,6 +130,46 @@ extern void *s_nan_data_lock;
#define NAN_KEY_INFO_ENC_KEY BIT(12) #define NAN_KEY_INFO_ENC_KEY BIT(12)
#define NAN_KEY_INFO_KEY_TYPE BIT(3) /* 1=Pairwise, 0=Group */ #define NAN_KEY_INFO_KEY_TYPE BIT(3) /* 1=Pairwise, 0=Group */
/* NAN KDE OUIs and Data Types carried in the Key Data field (Wi-Fi Aware
* v4.0 §9.5.21.5 Table 126; formats per 802.11 Fig 12-36/12-42/12-47). */
#define NAN_KDE_OUI_RSN_0 0x00
#define NAN_KDE_OUI_RSN_1 0x0F
#define NAN_KDE_OUI_RSN_2 0xAC
#define NAN_KDE_OUI_WFA_0 0x50
#define NAN_KDE_OUI_WFA_1 0x6F
#define NAN_KDE_OUI_WFA_2 0x9A
#define NAN_KDE_OUI_RSN 0x000FACUL
#define NAN_KDE_OUI_WFA 0x506F9AUL
#define NAN_KDE_TYPE_GTK 1 /* 00-0F-AC GTK KDE */
#define NAN_KDE_TYPE_MAC 3 /* 00-0F-AC MAC address KDE */
#define NAN_KDE_TYPE_IGTK 9 /* 00-0F-AC IGTK KDE */
#define NAN_KDE_TYPE_BIGTK 14 /* 00-0F-AC BIGTK KDE */
#define NAN_KDE_TYPE_NIK 36 /* 50-6F-9A NIK KDE */
#define NAN_KDE_TYPE_KEY_LIFE 37 /* 50-6F-9A NAN Key Lifetime KDE */
/* KDE inner-prefix lengths (bytes before the actual key material). */
#define NAN_KDE_HDR_LEN 6 /* DD(1) + len(1) + OUI(3) + DataType(1) */
#define NAN_GTK_KDE_PREFIX_LEN 2 /* KeyID/Tx(1) + Reserved(1) */
#define NAN_IGTK_KDE_PREFIX_LEN 8 /* KeyID(2) + IPN(6) */
#define NAN_BIGTK_KDE_PREFIX_LEN 8 /* KeyID(2) + BIPN(6) */
/* CSIA Capabilities group-SA support (§9.5.21.2 Table 122, bits 1-2, bit 2 high
* order). Mirrors hostap nan_defs.h NAN_CS_INFO_CAPA_GTK_SUPP_*. */
#define NAN_CSIA_CAP_GTK_SUPP_POS 1
#define NAN_CSIA_CAP_GTK_SUPP_MASK 0x06
#define NAN_CSIA_CAP_GTK_SUPP_NONE 0 /* 00: no group SA */
#define NAN_CSIA_CAP_GTK_SUPP_IGTK 1 /* 01: GTKSA + IGTKSA */
#define NAN_CSIA_CAP_GTK_SUPP_ALL 2 /* 10: GTKSA + IGTKSA + BIGTKSA */
/* ND-GTK is the data-path group key (CCMP-128). */
#define NAN_ND_GTK_LEN 16
/* Host-side bound for the group Key Data scratch buffers (plaintext + AES-wrap),
* sized for GTK+IGTK+BIGTK: GTK 24B + IGTK 30B + BIGTK 30B + optional Key Lifetime
* KDE(s), padded to a multiple of 8, + 8B NIST AES Key Wrap overhead (~104B worst
* case). NOT the descriptor-len reservation — the getters now return the EXACT
* per-NDP length, so the blob allocates exactly what the builder writes. */
#define NAN_GROUP_KEY_DATA_MAX 128
/* NCS-SK-128 only for now (Table 21): KCK 128 bits, KEK 128 bits, TK 128 bits, MIC 16 bytes */ /* 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_KCK_LEN 16
#define NAN_NCS_SK_128_KEK_LEN 16 #define NAN_NCS_SK_128_KEK_LEN 16
@@ -138,11 +178,21 @@ extern void *s_nan_data_lock;
#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) #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. */ /* Internal key-install constants matching esp_wifi_set_sta_key_internal semantics. */
#define NAN_WIFI_WPA_ALG_CCMP 3 #define NAN_WIFI_WPA_ALG_CCMP 3
#define NAN_WIFI_WPA_ALG_BIP_CMAC_128 7 /* IGTK/BIGTK BIP = blob WIFI_WPA_ALG_IGTK (confirmed by han2; 4 is SMS4) */
#define NAN_KEY_FLAG_RX BIT(2) #define NAN_KEY_FLAG_RX BIT(2)
#define NAN_KEY_FLAG_TX BIT(3) #define NAN_KEY_FLAG_TX BIT(3)
#define NAN_KEY_FLAG_PAIRWISE BIT(5) #define NAN_KEY_FLAG_PAIRWISE BIT(5)
/* NAN key-type selector passed as the last arg of esp_wifi_set_nan_key_internal;
* tells the blob which NAN SA the key belongs to. IGTK/BIGTK values are
* provisional — confirm with han2 before the lib bump. */
#define NAN_KEY_ND_TK 0
#define NAN_KEY_ND_GTK 1
#define NAN_KEY_NM_TK 2
#define NAN_KEY_ND_IGTK 3
#define NAN_KEY_ND_BIGTK 4
/* Handshake state */ /* Handshake state */
enum nan_handshake_state { enum nan_handshake_state {
NAN_HANDSHAKE_IDLE = 0, NAN_HANDSHAKE_IDLE = 0,
@@ -195,6 +245,13 @@ struct peer_svc_info {
* whose ND-PMKID matched the publisher SCIA. The initiator NDP-req path * whose ND-PMKID matched the publisher SCIA. The initiator NDP-req path
* uses this to pick the right credential for M1's pair-PMKID. */ * uses this to pick the right credential for M1's pair-PMKID. */
uint8_t matched_cred_idx; uint8_t matched_cred_idx;
/* Set at SDF match if the publisher advertised an NCS-GTK suite in its CSIA;
* the initiator NDP-req path uses it (with our own group_data_prot) to
* decide whether to distribute a GTK during NDP setup. */
uint8_t peer_group_data_cap: 1;
uint8_t peer_group_mgmt_cap: 1; /* peer advertised IGTKSA (CSIA caps bits 1-2 != 0) */
uint8_t peer_group_bigtk_cap: 1; /* peer advertised BIGTKSA (CSIA caps bits 1-2 == 10) */
uint8_t peer_sec_reserved: 5;
#endif #endif
#if CONFIG_ESP_WIFI_NAN_PAIRING #if CONFIG_ESP_WIFI_NAN_PAIRING
/* Peer NIK / cipher version / lifetime extracted from a NAN Shared Key /* Peer NIK / cipher version / lifetime extracted from a NAN Shared Key
@@ -273,20 +330,36 @@ struct ndl_info {
uint8_t handshake_state; uint8_t handshake_state;
/* Group key state (unsupported -- pairwise-only M1-M4 flow today; /* Received peer group keys (RX GTKSA). GTK protects group-addressed data
* fields kept to match the spec-defined RSNA key descriptor layout * frames the peer transmits; installed against the peer NDI. IGTK/BIGTK
* and stay forward-compatible). */ * protect group-addressed management/Beacon frames (NMI plane). */
uint8_t gtk[NAN_GTK_MAX_LEN]; uint8_t gtk[NAN_GTK_MAX_LEN];
uint8_t igtk[NAN_GTK_MAX_LEN]; uint8_t igtk[NAN_GTK_MAX_LEN];
uint8_t bigtk[NAN_GTK_MAX_LEN]; uint8_t bigtk[NAN_GTK_MAX_LEN];
uint8_t gtk_len; uint8_t gtk_len;
uint8_t igtk_len; uint8_t igtk_len;
uint8_t bigtk_len; uint8_t bigtk_len;
uint8_t gtk_keyid; /* peer GTK Key ID (1 or 2) */
uint8_t igtk_keyid; /* peer IGTK Key ID (4 or 5) */
uint8_t bigtk_keyid; /* peer BIGTK Key ID (6 or 7) */
uint8_t gtk_rsc[NAN_KEY_RSC_LEN]; /* peer GTK RSC from Key RSC field */
uint8_t gtk_set: 1; uint8_t gtk_set: 1;
uint8_t igtk_set: 1; uint8_t igtk_set: 1;
uint8_t bigtk_set: 1; uint8_t bigtk_set: 1;
uint8_t group_keys_reserved: 5; uint8_t group_keys_reserved: 5;
/* Own group key (TX GTKSA) distributed to the peer in M3 (initiator) or
* M4 (responder); installed against the local NDI as the TX group key. */
uint8_t own_gtk[NAN_ND_GTK_LEN];
uint8_t own_gtk_len;
uint8_t own_gtk_keyid; /* own GTK Key ID (1 or 2) */
uint8_t own_gtk_rsc[NAN_KEY_RSC_LEN];
uint8_t own_gtk_set: 1;
uint8_t gtk_required: 1; /* GTKSA negotiated for this NDP */
uint8_t igtk_required: 1; /* IGTKSA negotiated for this NDP */
uint8_t bigtk_required: 1; /* BIGTKSA negotiated for this NDP */
uint8_t own_group_reserved: 4;
uint8_t key_rsc[NAN_KEY_RSC_LEN]; uint8_t key_rsc[NAN_KEY_RSC_LEN];
#endif #endif
}; };
@@ -304,6 +377,28 @@ typedef struct {
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY #ifdef CONFIG_ESP_WIFI_NAN_SECURITY
uint8_t own_nik[ESP_WIFI_NAN_NIK_LEN]; uint8_t own_nik[ESP_WIFI_NAN_NIK_LEN];
bool own_nik_valid; bool own_nik_valid;
/* Device-global IGTKSA/BIGTKSA (one per NMI, §7.1.3.3/§7.1.3.4). Generated
* once via os_get_random and reused across all secured NDPs; copied into
* each M3/M4 and installed against the local NMI. BIP-CMAC-128 (16-byte).
* On ESP the NMI and NDI are the same MAC today. */
uint8_t own_igtk[NAN_ND_GTK_LEN];
uint8_t own_igtk_ipn[6];
uint8_t own_igtk_keyid; /* 4 or 5 */
bool own_igtk_set;
uint8_t own_bigtk[NAN_ND_GTK_LEN];
uint8_t own_bigtk_bipn[6];
uint8_t own_bigtk_keyid; /* 6 or 7 */
bool own_bigtk_set;
/* Device-global "support + use group management protection (IGTKSA/BIGTKSA)".
* One setting per NMI, not per service: Beacons are NMI-level and there is
* exactly one IGTKSA/BIGTKSA per NMI (§7.1.3.3/§7.1.3.4). Sourced from
* wifi_nan_sync_config_t.group_mgmt_prot at NAN start. When set it: forces
* GTKSA on every secured service (the CSIA caps field cannot encode IGTK/
* BIGTK without GTKSA, §9.5.21.2 Table 122), generates own IGTK+BIGTK,
* advertises caps 0x04, and BIP-protects Beacons + multicast SDFs.
* Advertising never blocks a non-supporting peer — keys and protection are
* set up only after capability negotiation (§7.1.3.5). */
bool group_mgmt_prot;
uint8_t cached_nira_nonce[8]; uint8_t cached_nira_nonce[8];
uint8_t cached_nira_tag[8]; uint8_t cached_nira_tag[8];
bool nira_cached; bool nira_cached;
@@ -340,7 +435,8 @@ bool nan_compute_service_id(const char *service_name, uint8_t service_id[6]);
uint32_t esp_nan_get_csia_len(uint16_t own_csid_bitmap, uint16_t peer_csid_bitmap); 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_scia_len(uint8_t num_pmkids);
uint32_t esp_nan_get_shared_key_desc_attr_len(uint16_t key_data_len); 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_ndp_security_install_get_shared_desc_len(uint8_t ndp_id, const uint8_t *peer_nmi);
int esp_nan_ndp_confirm_get_shared_desc_len(uint8_t ndp_id, const uint8_t *peer_nmi);
uint8_t esp_nan_get_ndp_resp_num_pmkids(uint8_t ndp_id, const uint8_t *peer_nmi); uint8_t esp_nan_get_ndp_resp_num_pmkids(uint8_t ndp_id, const uint8_t *peer_nmi);
uint32_t esp_nan_get_ndp_resp_shared_key_desc_len(uint8_t ndp_id, const uint8_t *peer_nmi); uint32_t esp_nan_get_ndp_resp_shared_key_desc_len(uint8_t ndp_id, const uint8_t *peer_nmi);
@@ -428,6 +524,11 @@ esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl,
const struct peer_svc_info *peer_svc, const struct peer_svc_info *peer_svc,
const uint8_t *peer_nmi); const uint8_t *peer_nmi);
/* Generate (once) and TX-install the device-global IGTK/BIGTK so Beacons and
* group-addressed SDFs are BIP-protected from NAN start (§7.1.3.3/§7.1.3.4).
* Call once at NAN start; never per-NDP (would reset the blob's BIPN counter). */
void nan_security_install_own_group_integrity_keys(void);
/* /*
* Match subscriber discovery security to a publisher's params. * Match subscriber discovery security to a publisher's params.
* NCS-SK: Compare locally derived ND-PMKID (subscriber passphrase, publisher NMI) to * NCS-SK: Compare locally derived ND-PMKID (subscriber passphrase, publisher NMI) to
@@ -510,6 +510,11 @@ int esp_nan_construct_nira(uint8_t *frm)
#define NAN_PASN_KDE_OUI_TYPE_LIFETIME 37 #define NAN_PASN_KDE_OUI_TYPE_LIFETIME 37
#define NAN_PASN_KEY_LIFETIME_NIK_BIT BIT(3) #define NAN_PASN_KEY_LIFETIME_NIK_BIT BIT(3)
#define NAN_ATTR_ID_SHARED_KEY_DESC 0x24 #define NAN_ATTR_ID_SHARED_KEY_DESC 0x24
/* iOS sends its NIK follow-up ~2.5-2.6 s after we derive the NM-TK; a 2 s window
* fired first and destructively removed the peer, so the late NIK arrived after
* teardown and the follow-up went out unprotected -> peer never started the NDP.
* 5 s lets the NIK land in time, so the cancel in the store-NIK path keeps the
* peer SA intact. */
#define NAN_PAIRING_NIK_FUP_TIMEOUT_SEC 5 #define NAN_PAIRING_NIK_FUP_TIMEOUT_SEC 5
struct nan_pairing_fup_ctx { struct nan_pairing_fup_ctx {
@@ -24,6 +24,7 @@
#include "esp_nan.h" #include "esp_nan.h"
#include "utils/common.h" #include "utils/common.h"
#include "crypto/sha256.h" #include "crypto/sha256.h"
#include "crypto/aes_wrap.h"
#include "nan_i.h" #include "nan_i.h"
static const char *TAG = "nan_sec"; static const char *TAG = "nan_sec";
@@ -56,6 +57,8 @@ static struct {
static struct { static struct {
bool has_pmkid; bool has_pmkid;
bool has_csid; bool has_csid;
bool peer_group_data; /* peer signalled group-data (GTK) support in its CSIA */
uint8_t peer_caps; /* raw CSIA Capabilities byte; IGTK/BIGTK gating derives bits 1-2 */
uint8_t pub_id; uint8_t pub_id;
uint8_t pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN]; uint8_t pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN];
uint16_t csid_bitmap; uint16_t csid_bitmap;
@@ -64,6 +67,12 @@ static struct {
/* Spec Table 121: valid CSIDs are 1..8. Bit 0 and bits 9..15 are not assigned. */ /* Spec Table 121: valid CSIDs are 1..8. Bit 0 and bits 9..15 are not assigned. */
#define NAN_CSID_VALID_BITMAP ((uint16_t)0x01FE) #define NAN_CSID_VALID_BITMAP ((uint16_t)0x01FE)
/* NCS-GTK cipher-suite bits (group-addressed data). Advertised when a service
* sets group_data_prot; the basic default we generate/advertise is CCM-128. */
#define NAN_CSID_GTK_BITS (WIFI_NAN_CSID_BIT_NCS_GTK_CCM_128 | \
WIFI_NAN_CSID_BIT_NCS_GTK_GCM_256)
#define NAN_CSID_GTK_DEFAULT WIFI_NAN_CSID_BIT_NCS_GTK_CCM_128
/* Sentinel for peer_svc->matched_cred_idx: no PMKID match remembered yet. */ /* Sentinel for peer_svc->matched_cred_idx: no PMKID match remembered yet. */
#define NAN_NO_MATCHED_CRED 0xFF #define NAN_NO_MATCHED_CRED 0xFF
@@ -458,6 +467,34 @@ static bool nan_security_fill_from_paired_cache(struct ndl_info *ndl, const uint
return false; return false;
} }
/* Early-reject: if the NDP Request carried an ND-PMKID, our cached ND-PMK must reproduce it (§7.1.3.5) before we commit. */
static const uint8_t zero_pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN] = {0};
if (memcmp(ndl->security_ctx.nd_pmkid, zero_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN) != 0) {
uint8_t our_nmi[6];
uint8_t service_id[6];
struct own_svc_info *own_svc = nan_find_own_svc(ndl->publisher_id);
if (esp_wifi_get_mac(WIFI_IF_NAN, our_nmi) != ESP_OK || !own_svc ||
!own_svc->svc_name[0] || !nan_compute_service_id(own_svc->svc_name, service_id)) {
ESP_LOGW(TAG, "Paired ND-PMK: cannot verify peer ND-PMKID (own NMI/service unavailable for pub_id=%u), deferring to handshake MIC",
ndl->publisher_id);
} else {
uint8_t expected[ESP_WIFI_NAN_NDP_PMKID_LEN];
/* Spec order (Initiator=peer, Responder=us), then reversed for interop, mirroring nan_match_pmkid(). */
bool ok = (nan_derive_ndp_request_pmkid(paired->nd_pmk, peer_nmi, our_nmi, service_id, expected) &&
memcmp(expected, ndl->security_ctx.nd_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN) == 0) ||
(nan_derive_ndp_request_pmkid(paired->nd_pmk, our_nmi, peer_nmi, service_id, expected) &&
memcmp(expected, ndl->security_ctx.nd_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN) == 0);
if (!ok) {
ESP_LOGE(TAG, "Paired ND-PMK rejected for peer "MACSTR": NDP-Request ND-PMKID does not match cached pairing key (csid=%u), secured NDP setup aborted",
MAC2STR(peer_nmi), paired->ndp_csid);
ESP_LOG_BUFFER_HEXDUMP(TAG, ndl->security_ctx.nd_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN, ESP_LOG_WARN);
return false;
}
ESP_LOGD(TAG, "Paired ND-PMK: peer ND-PMKID verified for "MACSTR, MAC2STR(peer_nmi));
}
}
ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED; ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED;
ndl->security_ctx.csid_bitmap = (uint16_t)(1u << paired->ndp_csid); ndl->security_ctx.csid_bitmap = (uint16_t)(1u << paired->ndp_csid);
memcpy(ndl->security_ctx.nd_pmk, paired->nd_pmk, ESP_WIFI_NAN_NDP_PMK_LEN); memcpy(ndl->security_ctx.nd_pmk, paired->nd_pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
@@ -604,6 +641,330 @@ static int nan_build_rsna_key_descriptor(uint8_t *kd, uint16_t key_info_flags,
return NAN_KEY_DESC_MIN_LEN; return NAN_KEY_DESC_MIN_LEN;
} }
/*-------------------------------------------------------------------------
* Group Key Data (GTK/IGTK/BIGTK KDEs) — Wi-Fi Aware v4.0 §7.1.3.2/§7.1.3.5/
* §9.5.21.5. Initiator distributes in M3, responder in M4; KDEs are always
* KEK-wrapped (NIST AES Key Wrap), never in clear. Structure mirrors hostap
* src/nan/nan_sec.c nan_sec_add_kdes(); IGTK/BIGTK are placeholders for now.
*-----------------------------------------------------------------------*/
/* True if a GTKSA was negotiated for this NDP. gtk_required is the explicit
* result of (our service has group_data_prot) AND (peer advertised NCS-GTK),
* computed at NDL finalize on both roles. We deliberately do NOT also gate on
* security_ctx.csid_bitmap: that field carries the advertised GTK bit on some
* paths and would over-fire when only one side enabled group protection. */
static bool nan_ndl_gtk_negotiated(const struct ndl_info *ndl)
{
return ndl->gtk_required;
}
/* IGTK/BIGTK negotiation gates. Per §7.1.3.5: include the IGTK/BIGTK KDE iff BOTH
* peers advertise the capability in their CSIA. igtk_required/bigtk_required are
* set at NDL finalize on both roles from (our service has group_mgmt_prot) AND
* (peer advertised IGTKSA/BIGTKSA in its CSIA caps byte). */
static bool nan_ndl_igtk_negotiated(const struct ndl_info *ndl)
{
return ndl->igtk_required;
}
static bool nan_ndl_bigtk_negotiated(const struct ndl_info *ndl)
{
return ndl->bigtk_required;
}
/* Lazily generate the local data-path GTK (CCMP-128). Fresh GTK starts at RSC 0. */
static int nan_ensure_own_gtk(struct ndl_info *ndl)
{
if (ndl->own_gtk_set) {
return 0;
}
if (os_get_random(ndl->own_gtk, NAN_ND_GTK_LEN) != 0) {
return -1;
}
ndl->own_gtk_len = NAN_ND_GTK_LEN;
ndl->own_gtk_keyid = 1; /* spec allows Key ID 1 or 2 */
memset(ndl->own_gtk_rsc, 0, NAN_KEY_RSC_LEN);
ndl->own_gtk_set = 1;
return 0;
}
/* Lazily generate the device-global IGTK/BIGTK (BIP-CMAC-128, §7.1.3.3/§7.1.3.4).
* Exactly one key per local NMI, reused across all secured NDPs; IPN/BIPN start
* at 0. Generated by this device (transmitter) per spec. */
static int nan_ensure_own_igtk(void)
{
if (s_nan_ctx.own_igtk_set) {
return 0;
}
if (os_get_random(s_nan_ctx.own_igtk, NAN_ND_GTK_LEN) != 0) {
return -1;
}
s_nan_ctx.own_igtk_keyid = 4; /* spec allows Key ID 4 or 5 */
memset(s_nan_ctx.own_igtk_ipn, 0, sizeof(s_nan_ctx.own_igtk_ipn));
s_nan_ctx.own_igtk_set = true;
return 0;
}
static int nan_ensure_own_bigtk(void)
{
if (s_nan_ctx.own_bigtk_set) {
return 0;
}
if (os_get_random(s_nan_ctx.own_bigtk, NAN_ND_GTK_LEN) != 0) {
return -1;
}
s_nan_ctx.own_bigtk_keyid = 6; /* spec allows Key ID 6 or 7 */
memset(s_nan_ctx.own_bigtk_bipn, 0, sizeof(s_nan_ctx.own_bigtk_bipn));
s_nan_ctx.own_bigtk_set = true;
return 0;
}
/* Generate (once) and TX-install the device-global IGTK/BIGTK at NAN start, so
* Beacons (BIGTK) and group-addressed SDFs (IGTK) are BIP-protected from the
* first frame — matching peers (e.g. iOS) that protect from NAN start, not just
* after the first NDP. The keys are device-global per §7.1.3.3/§7.1.3.4 and the
* same bytes are later distributed KEK-wrapped in M3/M4. Install MUST happen
* only here (not per-NDP), else re-installing with IPN/BIPN=0 would reset the
* blob's monotonic replay counter mid-session. Best-effort: a failed install
* warns but does not block NAN start. */
void nan_security_install_own_group_integrity_keys(void)
{
uint8_t own_nmi[6];
if (!s_nan_ctx.group_mgmt_prot) {
return; /* group-management protection disabled device-wide */
}
if (esp_wifi_get_mac(WIFI_IF_NAN, own_nmi) != ESP_OK) {
ESP_LOGW(TAG, "NAN start: get NMI failed; own IGTK/BIGTK TX not installed");
return;
}
if (nan_ensure_own_igtk() == 0 && s_nan_ctx.own_igtk_set) {
int r = esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_BIP_CMAC_128,
own_nmi, s_nan_ctx.own_igtk_keyid, 1,
s_nan_ctx.own_igtk_ipn, sizeof(s_nan_ctx.own_igtk_ipn),
s_nan_ctx.own_igtk, NAN_ND_GTK_LEN,
NAN_KEY_ND_IGTK);
if (r != 0) {
ESP_LOGW(TAG, "NAN start: own IGTK (TX) install failed, rc=0x%x", r);
} else {
ESP_LOGI(TAG, "NAN start: own IGTK (TX) installed (keyid=%d)", s_nan_ctx.own_igtk_keyid);
}
ESP_LOG_BUFFER_HEXDUMP("## ND-IGTK ", s_nan_ctx.own_igtk, NAN_ND_GTK_LEN, ESP_LOG_INFO);
}
if (nan_ensure_own_bigtk() == 0 && s_nan_ctx.own_bigtk_set) {
int r = esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_BIP_CMAC_128,
own_nmi, s_nan_ctx.own_bigtk_keyid, 1,
s_nan_ctx.own_bigtk_bipn, sizeof(s_nan_ctx.own_bigtk_bipn),
s_nan_ctx.own_bigtk, NAN_ND_GTK_LEN,
NAN_KEY_ND_BIGTK);
if (r != 0) {
ESP_LOGW(TAG, "NAN start: own BIGTK (TX) install failed, rc=0x%x", r);
} else {
ESP_LOGI(TAG, "NAN start: own BIGTK (TX) installed (keyid=%d)", s_nan_ctx.own_bigtk_keyid);
}
ESP_LOG_BUFFER_HEXDUMP("## ND-BIGTK ", s_nan_ctx.own_bigtk, NAN_ND_GTK_LEN, ESP_LOG_INFO);
}
}
/* NAN KDE header (802.11 Fig 12-34: DD len OUI(3) DataType(1)); mirrors hostap
* nan_add_kde_hdr(). data_len excludes the DD/len/OUI/type bytes. */
static uint8_t *nan_kde_put_hdr(uint8_t *p, uint8_t data_type, uint8_t data_len)
{
*p++ = 0xDD;
*p++ = (uint8_t)(4 + data_len); /* OUI(3) + DataType(1) + data */
*p++ = NAN_KDE_OUI_RSN_0;
*p++ = NAN_KDE_OUI_RSN_1;
*p++ = NAN_KDE_OUI_RSN_2;
*p++ = data_type;
return p;
}
/* IGTK KDE (§9.5.21.5: KeyID(2 LE) IPN(6) IGTK); mirrors hostap nan_sec_igtk_kde().
* Carries the device-global IGTK (BIP-CMAC-128); the peer installs it RX-only to
* verify our group-addressed management frames. */
static int nan_append_igtk_kde(struct ndl_info *ndl, uint8_t **p, const uint8_t *end)
{
if (!nan_ndl_igtk_negotiated(ndl)) {
return 0;
}
if ((size_t)(end - *p) < NAN_KDE_HDR_LEN + NAN_IGTK_KDE_PREFIX_LEN + NAN_ND_GTK_LEN) {
return -1;
}
uint8_t *q = nan_kde_put_hdr(*p, NAN_KDE_TYPE_IGTK,
NAN_IGTK_KDE_PREFIX_LEN + NAN_ND_GTK_LEN);
*q++ = s_nan_ctx.own_igtk_keyid & 0xFF; *q++ = 0; /* KeyID (2, LE) — 4/5 */
memcpy(q, s_nan_ctx.own_igtk_ipn, 6); q += 6; /* IPN (6) */
memcpy(q, s_nan_ctx.own_igtk, NAN_ND_GTK_LEN); q += NAN_ND_GTK_LEN;
*p = q;
return 0;
}
/* BIGTK KDE (§9.5.21.5: KeyID(2 LE) BIPN(6) BIGTK); mirrors hostap nan_sec_bigtk_kde().
* Carries the device-global BIGTK (BIP-CMAC-128); the peer installs it RX-only to
* verify our protected Beacon frames. */
static int nan_append_bigtk_kde(struct ndl_info *ndl, uint8_t **p, const uint8_t *end)
{
if (!nan_ndl_bigtk_negotiated(ndl)) {
return 0;
}
if ((size_t)(end - *p) < NAN_KDE_HDR_LEN + NAN_BIGTK_KDE_PREFIX_LEN + NAN_ND_GTK_LEN) {
return -1;
}
uint8_t *q = nan_kde_put_hdr(*p, NAN_KDE_TYPE_BIGTK,
NAN_BIGTK_KDE_PREFIX_LEN + NAN_ND_GTK_LEN);
*q++ = s_nan_ctx.own_bigtk_keyid & 0xFF; *q++ = 0; /* KeyID (2, LE) — 6/7 */
memcpy(q, s_nan_ctx.own_bigtk_bipn, 6); q += 6; /* BIPN (6) */
memcpy(q, s_nan_ctx.own_bigtk, NAN_ND_GTK_LEN); q += NAN_ND_GTK_LEN;
*p = q;
return 0;
}
/* GTK KDE (§7.1.3.2/§9.5.21.5, 802.11 Fig 12-36); mirrors hostap nan_sec_gtk_kde().
* Tx bit stays 0: the peer installs this as an RX-only group key. */
static int nan_append_gtk_kde(struct ndl_info *ndl, uint8_t **p, const uint8_t *end)
{
if (!ndl->own_gtk_set || !ndl->own_gtk_len) {
return 0;
}
if (ndl->own_gtk_keyid > 3) { /* CCMP/GCMP Key ID range (§7.1.3.2: 1 or 2) */
ESP_LOGW(TAG, "GTK: invalid Key ID %u", ndl->own_gtk_keyid);
return -1;
}
if ((size_t)(end - *p) < NAN_KDE_HDR_LEN + NAN_GTK_KDE_PREFIX_LEN + ndl->own_gtk_len) {
return -1;
}
uint8_t *q = nan_kde_put_hdr(*p, NAN_KDE_TYPE_GTK,
NAN_GTK_KDE_PREFIX_LEN + ndl->own_gtk_len);
*q++ = ndl->own_gtk_keyid & 0x03; /* KeyID (b0-1); Tx (b2)=0; b3-7 reserved */
*q++ = 0; /* Reserved */
memcpy(q, ndl->own_gtk, ndl->own_gtk_len);
*p = q + ndl->own_gtk_len;
return 0;
}
/* NIST AES Key Wrap of Key Data with the ND-KEK. Pads the plaintext to >=16
* octets and a multiple of 8 (0xDD then 0x00, per §12.7.2). Cipher = padded+8. */
static int nan_kek_wrap_key_data(struct ndl_info *ndl, const uint8_t *plain,
size_t plain_len, uint8_t *out, size_t out_cap,
size_t *out_len)
{
uint8_t pad[NAN_GROUP_KEY_DATA_MAX];
size_t padded = plain_len;
if (padded < 16) {
padded = 16;
}
if (padded % 8) {
padded = (padded + 7) & ~((size_t)7);
}
if (padded > sizeof(pad) || (padded + 8) > out_cap) {
return -1;
}
memcpy(pad, plain, plain_len);
if (padded > plain_len) {
pad[plain_len] = 0xDD;
memset(pad + plain_len + 1, 0, padded - plain_len - 1);
}
if (aes_wrap(ndl->nd_kek, ndl->kek_len, (int)(padded / 8), pad, out) != 0) {
return -1;
}
*out_len = padded + 8;
return 0;
}
/* NIST AES Key Unwrap of received Key Data with the ND-KEK. Plain = cipher-8. */
static int nan_kek_unwrap_key_data(struct ndl_info *ndl, const uint8_t *cipher,
size_t cipher_len, uint8_t *out, size_t out_cap,
size_t *out_len)
{
if (cipher_len < 24 || (cipher_len % 8) != 0) { /* >=16 plaintext + 8 wrap */
return -1;
}
size_t plain_len = cipher_len - 8;
if (plain_len > out_cap) {
return -1;
}
if (aes_unwrap(ndl->nd_kek, ndl->kek_len, (int)(plain_len / 8), cipher, out) != 0) {
return -1;
}
*out_len = plain_len;
return 0;
}
/* Build the local group Key Data (KDE order IGTK, BIGTK, GTK per upstream),
* KEK-wrap it, and patch Encrypted-Data bit + Key Data Length + Key RSC into
* the 95-byte descriptor in place. Returns the extended descriptor length, or
* NAN_KEY_DESC_MIN_LEN (pairwise-only) on negotiation-off or any error so the
* handshake still completes. Mirrors hostap nan_sec_add_kdes(); §7.1.3.5: KDEs
* are sent only KEK-wrapped, never in clear. */
static int nan_append_own_group_kdes(struct ndl_info *ndl, uint8_t *key_desc, size_t desc_cap)
{
bool want_gtk = nan_ndl_gtk_negotiated(ndl);
bool want_igtk = nan_ndl_igtk_negotiated(ndl);
bool want_bigtk = nan_ndl_bigtk_negotiated(ndl);
if (!want_gtk && !want_igtk && !want_bigtk) {
return NAN_KEY_DESC_MIN_LEN;
}
if (!ndl->ptk_set) {
ESP_LOGW(TAG, "Group KDEs [%s%s%s]: PTK/KEK not set; sending without group keys",
want_gtk ? "GTK " : "", want_igtk ? "IGTK " : "", want_bigtk ? "BIGTK " : "");
return NAN_KEY_DESC_MIN_LEN;
}
if (want_gtk && nan_ensure_own_gtk(ndl) != 0) {
ESP_LOGW(TAG, "GTK: own key generation failed; sending without group keys");
return NAN_KEY_DESC_MIN_LEN;
}
if (want_igtk && nan_ensure_own_igtk() != 0) {
ESP_LOGW(TAG, "IGTK: own key generation failed; sending without group keys");
return NAN_KEY_DESC_MIN_LEN;
}
if (want_bigtk && nan_ensure_own_bigtk() != 0) {
ESP_LOGW(TAG, "BIGTK: own key generation failed; sending without group keys");
return NAN_KEY_DESC_MIN_LEN;
}
uint8_t plain[NAN_GROUP_KEY_DATA_MAX];
uint8_t *p = plain;
const uint8_t *end = plain + sizeof(plain);
if (nan_append_igtk_kde(ndl, &p, end) < 0 ||
nan_append_bigtk_kde(ndl, &p, end) < 0 ||
nan_append_gtk_kde(ndl, &p, end) < 0) {
ESP_LOGW(TAG, "Group KDEs [%s%s%s]: assembly failed; sending without group keys",
want_gtk ? "GTK " : "", want_igtk ? "IGTK " : "", want_bigtk ? "BIGTK " : "");
return NAN_KEY_DESC_MIN_LEN;
}
size_t plain_len = (size_t)(p - plain);
if (plain_len == 0) {
return NAN_KEY_DESC_MIN_LEN; /* nothing negotiated to send */
}
size_t wrapped_len = 0;
if (nan_kek_wrap_key_data(ndl, plain, plain_len,
&key_desc[NAN_KEY_DESC_DATA_OFF],
desc_cap > NAN_KEY_DESC_DATA_OFF ?
desc_cap - NAN_KEY_DESC_DATA_OFF : 0,
&wrapped_len) != 0) {
ESP_LOGW(TAG, "Group KDEs [%s%s%s]: KEK wrap failed or no headroom; sending without group keys",
want_gtk ? "GTK " : "", want_igtk ? "IGTK " : "", want_bigtk ? "BIGTK " : "");
return NAN_KEY_DESC_MIN_LEN;
}
uint16_t key_info = (key_desc[NAN_KEY_DESC_KEY_INFO_OFF] << 8) |
key_desc[NAN_KEY_DESC_KEY_INFO_OFF + 1];
key_info |= NAN_KEY_INFO_ENC_KEY;
key_desc[NAN_KEY_DESC_KEY_INFO_OFF] = (key_info >> 8) & 0xFF;
key_desc[NAN_KEY_DESC_KEY_INFO_OFF + 1] = key_info & 0xFF;
key_desc[NAN_KEY_DESC_DATA_LEN_OFF] = (wrapped_len >> 8) & 0xFF;
key_desc[NAN_KEY_DESC_DATA_LEN_OFF + 1] = wrapped_len & 0xFF;
/* Key RSC carries the GTK's RSC when a GTK KDE is present (§7.1.3.5). */
memcpy(&key_desc[NAN_KEY_DESC_RSC_OFF], ndl->own_gtk_rsc, NAN_KEY_RSC_LEN);
ESP_LOGI(TAG, "Group Key Data wrapped: %u plain -> %u wrapped bytes, KDEs=[%s%s%s]",
(unsigned)plain_len, (unsigned)wrapped_len,
want_gtk ? "GTK " : "", want_igtk ? "IGTK " : "", want_bigtk ? "BIGTK " : "");
return NAN_KEY_DESC_MIN_LEN + (int)wrapped_len;
}
/* /*
* Internal SCIA builder shared by Publish SDF and NDP-Response paths. * Internal SCIA builder shared by Publish SDF and NDP-Response paths.
* Per-entry layout: Len(2) + Type(1) + PubID(1) + Value(PMKID_LEN) = 20 bytes. * Per-entry layout: Len(2) + Type(1) + PubID(1) + Value(PMKID_LEN) = 20 bytes.
@@ -636,7 +997,12 @@ static int nan_build_scia_attr(uint8_t *frm, uint8_t pub_id,
p += ESP_WIFI_NAN_NDP_PMKID_LEN; p += ESP_WIFI_NAN_NDP_PMKID_LEN;
} }
return (int)(p - frm); int written = (int)(p - frm);
/*
ESP_LOGI(TAG, "SCIA construct: frm=%p wrote=%d (hdr3+20*num_pmkids, num_pmkids=%u)",
frm, written, num_pmkids);
*/
return written;
} }
/* /*
@@ -871,10 +1237,24 @@ esp_err_t nan_derive_security_params(const char *service_name,
} }
out_derived[i].type = WIFI_NAN_SECURITY_ENCRYPTED; out_derived[i].type = WIFI_NAN_SECURITY_ENCRYPTED;
out_derived[i].csid_bitmap = (uint16_t)(1u << cred->csid); out_derived[i].csid_bitmap = (uint16_t)(1u << cred->csid);
/* Advertise the basic-default NCS-GTK suite alongside the credential's
* PMK cipher when the service enables group-addressed data protection.
* The blob unions derived_security[].csid_bitmap into the on-air CSIA,
* so this is what makes us announce GTK support (§7.1.3.5). The bit is
* masked back out for the pairwise/link cipher query (see
* nan_get_ndp_security_csid). */
if (sec_cfg->group_data_prot) {
out_derived[i].csid_bitmap |= NAN_CSID_GTK_DEFAULT;
}
out_derived[i].group_data_prot = sec_cfg->group_data_prot; out_derived[i].group_data_prot = sec_cfg->group_data_prot;
out_derived[i].group_mgmt_prot = sec_cfg->group_mgmt_prot; out_derived[i].group_mgmt_prot = sec_cfg->group_mgmt_prot;
} }
if (sec_cfg->group_data_prot) {
ESP_LOGI(TAG, "NAN GTK: advertising NCS-GTK-CCM-128 (group_data_prot=1) for svc='%s'",
service_name);
}
/* Mirror the derived material into the host's own_svc_info slot for this /* Mirror the derived material into the host's own_svc_info slot for this
* service (claimed before the blob's publish/subscribe call). This lets * service (claimed before the blob's publish/subscribe call). This lets
* host paths — nan_match_pmkid (responder M1) and the NDL seeding at * host paths — nan_match_pmkid (responder M1) and the NDL seeding at
@@ -913,6 +1293,16 @@ uint16_t nan_get_ndp_security_csid(uint8_t ndp_id, const uint8_t *peer_nmi)
struct ndl_info *ndl = nan_find_ndl(ndp_id, (uint8_t *)peer_nmi); struct ndl_info *ndl = nan_find_ndl(ndp_id, (uint8_t *)peer_nmi);
uint16_t csid = ndl ? ndl->security_ctx.csid_bitmap : 0; uint16_t csid = ndl ? ndl->security_ctx.csid_bitmap : 0;
NAN_DATA_UNLOCK(); NAN_DATA_UNLOCK();
/* Return the FULL negotiated bitmap, INCLUDING NCS-GTK when group-addressed
* data protection is in use, so the on-air NDP Request/Response CSIA
* advertises the group cipher and the peer can detect our group-data support
* (required for symmetric GTK distribution and third-party/iOS/Android
* interop). Safe to include NCS-GTK here: the blob treats this value as an
* opaque bitmap that it passes straight to the host CSIA callbacks
* (construct_csia / get_csia_len) and never interprets individual bits
* (confirmed by han2). Pairwise/link cipher selection and ND-TK install are
* host-driven and do not read this field; the group key has its own install
* path (NAN_KEY_ND_GTK) and gtk_required gate. */
return csid; return csid;
} }
@@ -933,6 +1323,19 @@ bool nan_security_service_match(const struct own_svc_info *own_svc,
const wifi_nan_discovery_security_params_t *cfg = &own_svc->user_cfg; const wifi_nan_discovery_security_params_t *cfg = &own_svc->user_cfg;
/* Remember whether this publisher signalled group-data (GTK) support, so the
* initiator NDP-req path can gate GTK distribution on mutual support. The
* spec-correct signal is the CSIA Capabilities byte (parsed into
* group_data_prot by esp_nan_parse_publish_security); an NCS-GTK cipher-suite
* ID is the legacy Android/ESP signal and is OR'd in for interop. */
peer_svc->peer_group_data_cap = (peer_sec->group_data_prot ||
(peer_sec->csid_bitmap & NAN_CSID_GTK_BITS)) ? 1 : 0;
/* IGTK/BIGTK (group management) support comes from the CSIA Capabilities
* byte (parsed into group_mgmt_prot/group_bigtk_prot by
* esp_nan_parse_publish_security): IGTKSA = bits 1-2 != 0, BIGTKSA = 10. */
peer_svc->peer_group_mgmt_cap = peer_sec->group_mgmt_prot ? 1 : 0;
peer_svc->peer_group_bigtk_cap = peer_sec->group_bigtk_prot ? 1 : 0;
if (cfg->num_credentials == 0 || if (cfg->num_credentials == 0 ||
cfg->num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) { cfg->num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) {
return false; return false;
@@ -1117,6 +1520,35 @@ esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl,
ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED; ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED;
ndl->security_ctx.csid_bitmap = (uint16_t)(1u << ndp_csid); ndl->security_ctx.csid_bitmap = (uint16_t)(1u << ndp_csid);
/* Distribute a GTK in M3 only if we enabled group_data_prot AND the
* publisher advertised an NCS-GTK suite (recorded at SDF match). */
ndl->gtk_required = (cfg->group_data_prot && peer_svc &&
peer_svc->peer_group_data_cap) ? 1 : 0;
/* Advertise NCS-GTK in the NDP-Request CSIA so any responder (incl.
* third-party/iOS/Android) can detect our group-data support and
* reciprocate. Carried in ndl->security_ctx.csid_bitmap, which
* nan_get_ndp_security_csid() returns verbatim to the blob for the on-air
* M1/M2 CSIA. Pairwise cipher selection / ND-TK install do not read this
* field, so including the group bit here is safe. */
if (ndl->gtk_required) {
ndl->security_ctx.csid_bitmap |= NAN_CSID_GTK_DEFAULT;
}
if (cfg->group_data_prot) {
ESP_LOGI(TAG, "NDP GTK: %s (initiator) - own group_prot=1, peer NCS-GTK=%d",
ndl->gtk_required ? "negotiated" : "NOT negotiated",
(peer_svc && peer_svc->peer_group_data_cap) ? 1 : 0);
}
/* IGTK distributed in M3 iff we enabled group_mgmt_prot AND the publisher
* advertised IGTKSA; BIGTK additionally requires BIGTKSA (§7.1.3.5). */
bool peer_igtk = peer_svc && peer_svc->peer_group_mgmt_cap;
bool peer_bigtk = peer_svc && peer_svc->peer_group_bigtk_cap;
ndl->igtk_required = (s_nan_ctx.group_mgmt_prot && peer_igtk) ? 1 : 0;
ndl->bigtk_required = (s_nan_ctx.group_mgmt_prot && peer_bigtk) ? 1 : 0;
if (s_nan_ctx.group_mgmt_prot) {
ESP_LOGI(TAG, "NDP IGTK/BIGTK: igtk=%s bigtk=%s (initiator) - peer igtk=%d bigtk=%d",
ndl->igtk_required ? "yes" : "no", ndl->bigtk_required ? "yes" : "no",
peer_igtk, peer_bigtk);
}
memcpy(ndl->security_ctx.nd_pmk, pmk, ESP_WIFI_NAN_NDP_PMK_LEN); memcpy(ndl->security_ctx.nd_pmk, pmk, ESP_WIFI_NAN_NDP_PMK_LEN);
memcpy(ndl->security_ctx.nd_pmkid, pair_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN); memcpy(ndl->security_ctx.nd_pmkid, pair_pmkid, ESP_WIFI_NAN_NDP_PMKID_LEN);
@@ -1153,8 +1585,27 @@ int esp_nan_construct_csia(uint8_t *frm, uint8_t pub_id, uint16_t own_csid_bitma
*p++ = attr_len & 0xFF; *p++ = attr_len & 0xFF;
*p++ = (attr_len >> 8) & 0xFF; *p++ = (attr_len >> 8) & 0xFF;
/* Capabilities byte (0x4 set for iOS interop) */ /* Capabilities group-SA bits (§9.5.21.2 Table 122) are strictly nested:
*p++ = 0x4; * 00 = none, 01 = GTKSA+IGTKSA, 10 = GTKSA+IGTKSA+BIGTKSA.
* There is no "IGTK/BIGTK without GTKSA" codepoint, so:
* - device-global group_mgmt_prot set -> 10 (BIGTK forces GTK+IGTK; we also
* force GTKSA on every secured service, see nan_claim_own_svc_slot);
* - else if this CSIA carries a GTK suite -> 01 (GTKSA mandates IGTKSA);
* - else -> 00.
* Advertising support never blocks a peer that lacks protection: the keys and
* frame protection are set up only AFTER mutual capability negotiation — the
* IGTK/BIGTK/GTK KDEs are exchanged iff BOTH peers advertise support
* (§7.1.3.5), and a non-supporting peer ignores the unknown MME elements and
* still connects. */
uint8_t grp_caps;
if (s_nan_ctx.group_mgmt_prot) {
grp_caps = (uint8_t)(NAN_CSIA_CAP_GTK_SUPP_ALL << NAN_CSIA_CAP_GTK_SUPP_POS); /* 0x04 (10) */
} else if (own_csid_bitmap & NAN_CSID_GTK_DEFAULT) {
grp_caps = (uint8_t)(NAN_CSIA_CAP_GTK_SUPP_IGTK << NAN_CSIA_CAP_GTK_SUPP_POS); /* 0x02 (01) */
} else {
grp_caps = NAN_CSIA_CAP_GTK_SUPP_NONE; /* 0x00 (00) */
}
*p++ = grp_caps;
/* Cipher Suite ID list: [CSID(1)] [Publish ID(1)] */ /* Cipher Suite ID list: [CSID(1)] [Publish ID(1)] */
for (uint8_t csid = 1; csid <= 8; csid++) { for (uint8_t csid = 1; csid <= 8; csid++) {
@@ -1164,7 +1615,12 @@ int esp_nan_construct_csia(uint8_t *frm, uint8_t pub_id, uint16_t own_csid_bitma
} }
} }
return (int)(p - frm); int written = (int)(p - frm);
/*
ESP_LOGI(TAG, "CSIA construct: frm=%p wrote=%d (hdr3+cap1+2*csids=%u, bitmap=0x%04x)",
frm, written, num_csids, csid_bitmap);
*/
return written;
} }
int esp_nan_construct_scia_publish(uint8_t *frm, uint8_t pub_id, int esp_nan_construct_scia_publish(uint8_t *frm, uint8_t pub_id,
@@ -1228,8 +1684,10 @@ uint32_t esp_nan_get_csia_len(uint16_t own_csid_bitmap, uint16_t peer_csid_bitma
} }
uint8_t num_csids = __builtin_popcount(csid_bitmap); uint8_t num_csids = __builtin_popcount(csid_bitmap);
uint32_t len = 3 + 1 + 2 * num_csids; uint32_t len = 3 + 1 + 2 * num_csids;
ESP_LOGD(TAG, "GET CSIA LEN: %lu (own=0x%04x, peer=0x%04x, num_csids=%d)", /*
len, own_csid_bitmap, peer_csid_bitmap, num_csids); ESP_LOGI(TAG, "CSIA len getter -> %lu (own=0x%04x, peer=0x%04x, effective=0x%04x, num_csids=%d)",
len, own_csid_bitmap, peer_csid_bitmap, csid_bitmap, num_csids);
*/
return len; return len;
} }
@@ -1243,7 +1701,7 @@ uint32_t esp_nan_get_scia_len(uint8_t num_pmkids)
return 0; return 0;
} }
uint32_t len = 3 + 20 * num_pmkids; uint32_t len = 3 + 20 * num_pmkids;
ESP_LOGD(TAG, "GET SCIA LEN: %lu (num_pmkids=%d)", len, num_pmkids); // ESP_LOGI(TAG, "SCIA len getter -> %lu (num_pmkids=%d)", len, num_pmkids);
return len; return len;
} }
@@ -1256,9 +1714,112 @@ uint32_t esp_nan_get_shared_key_desc_attr_len(uint16_t key_data_len)
return total; return total;
} }
int esp_nan_ndp_security_install_get_shared_desc_len(void) /* Which group KDEs a key descriptor carries (for sizing + logging). */
#define NAN_KDE_PRESENT_GTK BIT(0)
#define NAN_KDE_PRESENT_IGTK BIT(1)
#define NAN_KDE_PRESENT_BIGTK BIT(2)
/* Exact wrapped group Key Data length this NDL will emit — mirrors what
* nan_append_own_group_kdes() writes, with NO side effects (does not generate the
* GTK). Sets *kde_mask to the included KDEs. 0 = no group keys (pairwise-only).
* Caller holds the lock. Keep in lockstep with nan_append_{igtk,bigtk,gtk}_kde(). */
static size_t nan_group_key_data_wrapped_len(const struct ndl_info *ndl, uint8_t *kde_mask)
{ {
return (int)esp_nan_get_shared_key_desc_attr_len(0); uint8_t mask = 0;
size_t plain = 0;
if (!ndl || !ndl->ptk_set) {
if (kde_mask) {
*kde_mask = 0;
}
return 0; /* no KEK yet -> nothing can be wrapped */
}
/* Order matches the builder: IGTK, BIGTK, GTK. Each branch contributes iff
* its negotiation gate (igtk/bigtk/gtk_required) fired for this NDP. */
if (nan_ndl_igtk_negotiated(ndl)) {
plain += NAN_KDE_HDR_LEN + NAN_IGTK_KDE_PREFIX_LEN + NAN_ND_GTK_LEN;
mask |= NAN_KDE_PRESENT_IGTK;
}
if (nan_ndl_bigtk_negotiated(ndl)) {
plain += NAN_KDE_HDR_LEN + NAN_BIGTK_KDE_PREFIX_LEN + NAN_ND_GTK_LEN;
mask |= NAN_KDE_PRESENT_BIGTK;
}
if (nan_ndl_gtk_negotiated(ndl)) {
plain += NAN_KDE_HDR_LEN + NAN_GTK_KDE_PREFIX_LEN + NAN_ND_GTK_LEN;
mask |= NAN_KDE_PRESENT_GTK;
}
if (kde_mask) {
*kde_mask = mask;
}
if (plain == 0) {
return 0;
}
size_t padded = plain < 16 ? 16 : plain; /* NIST AES Key Wrap minimum */
if (padded % 8) {
padded = (padded + 7) & ~((size_t)7);
}
return padded + 8; /* + AES-Key-Wrap overhead */
}
/* INFO log of the descriptor length and which group KDEs it carries, so on-device
* logs show what was sized/sent (not a silent length). */
static void nan_log_group_desc_len(const char *msg, uint8_t ndp_id, int ret,
uint16_t key_data_len, uint8_t kde_mask,
bool found, bool ptk_set)
{
if (!found) {
ESP_LOGW(TAG, "%s desc len: no NDL (ndp_id=%d) -> len=%d (no Key Data)",
msg, ndp_id, ret);
} else if (key_data_len == 0) {
ESP_LOGI(TAG, "%s desc len=%d (ndp_id=%d): no group KDEs (ptk_set=%d)",
msg, ret, ndp_id, ptk_set);
} else {
ESP_LOGI(TAG, "%s desc len=%d (ndp_id=%d): Key Data=%u KDEs=[%s%s%s]",
msg, ret, ndp_id, key_data_len,
(kde_mask & NAN_KDE_PRESENT_GTK) ? "GTK " : "",
(kde_mask & NAN_KDE_PRESENT_IGTK) ? "IGTK " : "",
(kde_mask & NAN_KDE_PRESENT_BIGTK) ? "BIGTK " : "");
}
}
/* Exact M4 (NDP Security Install) Shared Key Descriptor length for this NDP, so the
* blob allocates exactly what the builder writes (no on-air zero-padding). */
int esp_nan_ndp_security_install_get_shared_desc_len(uint8_t ndp_id, const uint8_t *peer_nmi)
{
uint16_t key_data_len = 0;
uint8_t kde_mask = 0;
bool found = false, ptk = false;
NAN_DATA_LOCK();
struct ndl_info *ndl = nan_find_ndl(ndp_id, (uint8_t *)peer_nmi);
if (ndl) {
found = true;
ptk = ndl->ptk_set;
key_data_len = (uint16_t)nan_group_key_data_wrapped_len(ndl, &kde_mask);
}
NAN_DATA_UNLOCK();
int ret = (int)esp_nan_get_shared_key_desc_attr_len(key_data_len);
nan_log_group_desc_len("M4 Security Install", ndp_id, ret, key_data_len, kde_mask, found, ptk);
return ret;
}
/* Exact M3 (NDP Confirm) length for the initiator path; same contract as M4. */
int esp_nan_ndp_confirm_get_shared_desc_len(uint8_t ndp_id, const uint8_t *peer_nmi)
{
uint16_t key_data_len = 0;
uint8_t kde_mask = 0;
bool found = false, ptk = false;
NAN_DATA_LOCK();
struct ndl_info *ndl = nan_find_ndl(ndp_id, (uint8_t *)peer_nmi);
if (ndl) {
found = true;
ptk = ndl->ptk_set;
key_data_len = (uint16_t)nan_group_key_data_wrapped_len(ndl, &kde_mask);
}
NAN_DATA_UNLOCK();
int ret = (int)esp_nan_get_shared_key_desc_attr_len(key_data_len);
nan_log_group_desc_len("M3 Confirm", ndp_id, ret, key_data_len, kde_mask, found, ptk);
return ret;
} }
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_resp_shared_key_desc(uint8_t *buf, size_t buf_len, uint8_t ndp_id, const uint8_t *peer_nmi)
@@ -1377,14 +1938,12 @@ int esp_nan_get_ndp_security_install_key_desc(uint8_t *buf, size_t buf_len, uint
uint8_t *p = buf; uint8_t *p = buf;
*p++ = NAN_ATTR_ID_SHARED_KEY_DESC; *p++ = NAN_ATTR_ID_SHARED_KEY_DESC;
{ uint8_t *len_field = p; /* backpatched once Key Data length is known */
uint16_t body_len = 1 + NAN_KEY_DESC_MIN_LEN; p += 2;
*p++ = body_len & 0xFF;
*p++ = (body_len >> 8) & 0xFF;
}
*p++ = ndl->publisher_id; *p++ = ndl->publisher_id;
int n = nan_build_rsna_key_descriptor(p, uint8_t *key_desc = p;
int n = nan_build_rsna_key_descriptor(key_desc,
NAN_KEY_INFO_MIC | NAN_KEY_INFO_MIC |
NAN_KEY_INFO_SECURE | NAN_KEY_INFO_SECURE |
NAN_KEY_INFO_INSTALL, NAN_KEY_INFO_INSTALL,
@@ -1396,11 +1955,18 @@ int esp_nan_get_ndp_security_install_key_desc(uint8_t *buf, size_t buf_len, uint
return 0; return 0;
} }
n = 3 + 1 + n; /* Responder distributes its GTK in M4 (§7.1.3.2). Buffer headroom comes
* from esp_nan_ndp_security_install_get_shared_desc_len(); falls back to
* pairwise-only if the blob under-allocated the buffer. */
n = nan_append_own_group_kdes(ndl, key_desc, buf_len - 4);
uint16_t body_len = 1 + (uint16_t)n;
len_field[0] = body_len & 0xFF;
len_field[1] = (body_len >> 8) & 0xFF;
NAN_DATA_UNLOCK(); NAN_DATA_UNLOCK();
ESP_LOGD(TAG, "NDP M4 Key Desc: built for ndp_id=%d", ndp_id); ESP_LOGD(TAG, "NDP M4 Key Desc: built (key_desc=%d bytes) for ndp_id=%d", n, ndp_id);
return n; return 3 + 1 + n;
} }
int esp_nan_update_ndp_resp_mic(uint8_t *m2_body, size_t body_len, uint8_t *key_desc_attr, int esp_nan_update_ndp_resp_mic(uint8_t *m2_body, size_t body_len, uint8_t *key_desc_attr,
@@ -1456,6 +2022,24 @@ void esp_nan_parse_ndp_csia(void *frm, size_t buf_len, wifi_nan_security_params_
s_pending_scia.has_csid = true; s_pending_scia.has_csid = true;
s_pending_scia.pub_id = pub_id; s_pending_scia.pub_id = pub_id;
s_pending_scia.csid_bitmap = accum; s_pending_scia.csid_bitmap = accum;
/* Peer wants group-data (GTK) protection if it advertises GTKSA
* support in the CSIA Capabilities byte (body[0] bits 1-2,
* §9.5.21.2 Table 122 — how iOS signals it) or lists an NCS-GTK
* cipher suite (how Android/ESP signal it). The Capabilities byte
* is the spec-correct indicator; an NCS-GTK CSID only selects WHICH
* group cipher and need not be present. */
s_pending_scia.peer_group_data =
((body[0] & NAN_CSIA_CAP_GTK_SUPP_MASK) ||
(accum & NAN_CSID_GTK_BITS)) ? true : false;
if (s_pending_scia.peer_group_data) {
param->group_data_prot = 1;
}
/* Store the raw CSIA Capabilities byte; IGTK/BIGTK gating derives
* bits 1-2 (01=IGTKSA, 10=+BIGTKSA) independently at NDL finalize. */
s_pending_scia.peer_caps = body[0];
if (body[0] & NAN_CSIA_CAP_GTK_SUPP_MASK) {
param->group_mgmt_prot = 1;
}
} }
} }
} }
@@ -1612,56 +2196,97 @@ void esp_nan_parse_ndp_key_desc(void *frm, size_t buf_len, uint8_t ndp_id, const
memcpy(ndl->key_rsc, key_rsc, NAN_KEY_RSC_LEN); memcpy(ndl->key_rsc, key_rsc, NAN_KEY_RSC_LEN);
/* Parse Key Data (KDEs) - only when not encrypted */ /* Parse Key Data (KDEs). Per §7.1.3.5 group keys are always carried
if (key_data_len > 0 && (NAN_KEY_DESC_DATA_OFF + key_data_len <= key_desc_len) && !has_enc_key) { * KEK-wrapped, so decrypt with the ND-KEK before walking. The plaintext
uint8_t *key_data = &key_desc[NAN_KEY_DESC_DATA_OFF]; * may carry 0xDD/0x00 AES-Key-Wrap padding after the last KDE. */
if (key_data_len > 0 && (NAN_KEY_DESC_DATA_OFF + key_data_len <= key_desc_len)) {
uint8_t plain[NAN_GROUP_KEY_DATA_MAX];
const uint8_t *kde_buf = &key_desc[NAN_KEY_DESC_DATA_OFF];
size_t kde_len = key_data_len;
if (has_enc_key) {
size_t unwrapped = 0;
if (!ndl->ptk_set) {
ESP_LOGW(TAG, "NDP Key Desc: encrypted Key Data but PTK/KEK not set; skipping KDEs");
kde_len = 0;
} else if (nan_kek_unwrap_key_data(ndl, &key_desc[NAN_KEY_DESC_DATA_OFF],
key_data_len, plain, sizeof(plain),
&unwrapped) != 0) {
ESP_LOGW(TAG, "NDP Key Desc: KEK unwrap of Key Data failed; skipping KDEs");
kde_len = 0;
} else {
kde_buf = plain;
kde_len = unwrapped;
}
}
uint16_t kd_offset = 0; uint16_t kd_offset = 0;
while (kd_offset + 2 <= kde_len) {
while (kd_offset + 2 <= key_data_len) { uint8_t kde_id = kde_buf[kd_offset];
uint8_t kde_type = key_data[kd_offset]; uint8_t kde_flen = kde_buf[kd_offset + 1];
uint8_t kde_len = key_data[kd_offset + 1]; /* All KDEs start with 0xDD; a 0xDD/0x00 pad (or any short
* element) terminates the meaningful list. */
if (kd_offset + 2 + kde_len > key_data_len) { if (kde_id != 0xDD || kde_flen < 4 ||
kd_offset + 2 + kde_flen > kde_len) {
break; break;
} }
if (kde_type == 0xDD && kde_len >= 4) { uint32_t oui = (kde_buf[kd_offset + 2] << 16) |
uint32_t oui = (key_data[kd_offset + 2] << 16) | (key_data[kd_offset + 3] << 8) | key_data[kd_offset + 4]; (kde_buf[kd_offset + 3] << 8) | kde_buf[kd_offset + 4];
uint8_t data_type = key_data[kd_offset + 5]; uint8_t data_type = kde_buf[kd_offset + 5];
uint8_t *data = &key_data[kd_offset + 6]; const uint8_t *body = &kde_buf[kd_offset + 6];
uint8_t data_len = kde_len - 4; uint8_t body_len = kde_flen - 4; /* after OUI(3) + DataType(1) */
if (oui == 0x000FAC) { /* WFA OUI */ if (oui == NAN_KDE_OUI_RSN) {
if (data_type == 1 && data_len <= NAN_GTK_MAX_LEN) { if (data_type == NAN_KDE_TYPE_GTK && body_len > NAN_GTK_KDE_PREFIX_LEN) {
memcpy(ndl->gtk, data, data_len); /* GTK KDE: KeyID/Tx(1) Reserved(1) GTK(var) */
ndl->gtk_len = data_len; uint8_t gtk_len = body_len - NAN_GTK_KDE_PREFIX_LEN;
if (gtk_len <= NAN_GTK_MAX_LEN) {
ndl->gtk_keyid = body[0] & 0x03;
memcpy(ndl->gtk, body + NAN_GTK_KDE_PREFIX_LEN, gtk_len);
ndl->gtk_len = gtk_len;
memcpy(ndl->gtk_rsc, key_rsc, NAN_KEY_RSC_LEN);
ndl->gtk_set = 1; ndl->gtk_set = 1;
ESP_LOGI(TAG, "KDE: GTK stored (len=%d)", data_len); ESP_LOGI(TAG, "KDE: peer GTK stored (keyid=%d, len=%d)",
} else if (data_type == 3 && data_len >= 6) { ndl->gtk_keyid, gtk_len);
ESP_LOGI(TAG, "KDE: MAC Address: " MACSTR, MAC2STR(data));
} else if (data_type == 4 && data_len <= NAN_GTK_MAX_LEN) {
memcpy(ndl->igtk, data, data_len);
ndl->igtk_len = data_len;
ndl->igtk_set = 1;
ESP_LOGI(TAG, "KDE: IGTK stored (len=%d)", data_len);
} else if (data_type == 5 && data_len <= NAN_GTK_MAX_LEN) {
memcpy(ndl->bigtk, data, data_len);
ndl->bigtk_len = data_len;
ndl->bigtk_set = 1;
ESP_LOGI(TAG, "KDE: BIGTK stored (len=%d)", data_len);
} }
} else if (oui == 0x506F9A) { /* NAN OUI */ } else if (data_type == NAN_KDE_TYPE_MAC && body_len >= 6) {
if (data_type == 36 && data_len >= 1) { ESP_LOGI(TAG, "KDE: MAC Address: " MACSTR, MAC2STR(body));
ESP_LOGI(TAG, "KDE: NIK (Cipher Ver: %d)", data[0]); } else if (data_type == NAN_KDE_TYPE_IGTK && body_len > NAN_IGTK_KDE_PREFIX_LEN) {
} else if (data_type == 37 && data_len >= 6) { /* IGTK KDE: KeyID(2 LE) IPN(6) IGTK(var) */
uint16_t key_bitmap = data[0] | (data[1] << 8); uint8_t igtk_len = body_len - NAN_IGTK_KDE_PREFIX_LEN;
uint32_t lifetime = (data[2] << 24) | (data[3] << 16) | (data[4] << 8) | data[5]; if (igtk_len <= NAN_GTK_MAX_LEN) {
ESP_LOGI(TAG, "KDE: NAN Key Lifetime: %lu s, Bitmap: 0x%04x", ndl->igtk_keyid = body[0];
(unsigned long)lifetime, key_bitmap); memcpy(ndl->igtk, body + NAN_IGTK_KDE_PREFIX_LEN, igtk_len);
ndl->igtk_len = igtk_len;
ndl->igtk_set = 1;
ESP_LOGI(TAG, "KDE: peer IGTK stored (keyid=%d, len=%d)",
ndl->igtk_keyid, igtk_len);
}
} else if (data_type == NAN_KDE_TYPE_BIGTK && body_len > NAN_BIGTK_KDE_PREFIX_LEN) {
/* BIGTK KDE: KeyID(2 LE) BIPN(6) BIGTK(var) */
uint8_t bigtk_len = body_len - NAN_BIGTK_KDE_PREFIX_LEN;
if (bigtk_len <= NAN_GTK_MAX_LEN) {
ndl->bigtk_keyid = body[0];
memcpy(ndl->bigtk, body + NAN_BIGTK_KDE_PREFIX_LEN, bigtk_len);
ndl->bigtk_len = bigtk_len;
ndl->bigtk_set = 1;
ESP_LOGI(TAG, "KDE: peer BIGTK stored (keyid=%d, len=%d)",
ndl->bigtk_keyid, bigtk_len);
} }
} }
} else if (oui == NAN_KDE_OUI_WFA) {
if (data_type == NAN_KDE_TYPE_NIK && body_len >= 1) {
ESP_LOGI(TAG, "KDE: NIK (Cipher Ver: %d)", body[0]);
} else if (data_type == NAN_KDE_TYPE_KEY_LIFE && body_len >= 6) {
uint16_t key_bitmap = body[0] | (body[1] << 8);
uint32_t lifetime = (body[2] << 24) | (body[3] << 16) |
(body[4] << 8) | body[5];
ESP_LOGI(TAG, "KDE: NAN Key Lifetime: %lu s, Bitmap: 0x%04x",
(unsigned long)lifetime, key_bitmap);
}
} }
kd_offset += 2 + kde_len; kd_offset += 2 + kde_flen;
} }
} }
} else if (msg_type == 1) { } else if (msg_type == 1) {
@@ -1698,7 +2323,19 @@ esp_err_t esp_nan_parse_publish_security(const uint8_t *attrs, size_t attrs_len,
ESP_LOGD(TAG, "Found CSIA in Publish SDF, len=%d", csia_len); ESP_LOGD(TAG, "Found CSIA in Publish SDF, len=%d", csia_len);
ESP_LOG_BUFFER_HEXDUMP(TAG, csia, csia_len, ESP_LOG_DEBUG); ESP_LOG_BUFFER_HEXDUMP(TAG, csia, csia_len, ESP_LOG_DEBUG);
/* Skip Capabilities byte; iterate [CSID(1)] [Publish ID(1)] entries */ /* Capabilities byte (csia[0]) bits 1-2 = GTKSA/IGTKSA/BIGTKSA support
* (§9.5.21.2 Table 122): 01 = GTKSA+IGTKSA, 10 = +BIGTKSA. This — not an
* NCS-GTK cipher-suite ID — is how a peer (notably iOS) advertises it. */
uint8_t grp_supp = csia[0] & NAN_CSIA_CAP_GTK_SUPP_MASK;
if (grp_supp) {
security->group_data_prot = 1;
security->group_mgmt_prot = 1; /* IGTKSA */
}
if (grp_supp == (NAN_CSIA_CAP_GTK_SUPP_ALL << NAN_CSIA_CAP_GTK_SUPP_POS)) {
security->group_bigtk_prot = 1; /* BIGTKSA */
}
/* iterate [CSID(1)] [Publish ID(1)] entries after the Capabilities byte */
size_t offset = 1; size_t offset = 1;
while (offset + 2 <= csia_len) { while (offset + 2 <= csia_len) {
uint8_t csid = csia[offset]; uint8_t csid = csia[offset];
@@ -1710,6 +2347,12 @@ esp_err_t esp_nan_parse_publish_security(const uint8_t *attrs, size_t attrs_len,
offset += 2; offset += 2;
} }
/* Android/ESP peers advertise group-data support by listing an NCS-GTK
* cipher suite instead of (or with) the Capabilities byte bits. */
if (security->csid_bitmap & NAN_CSID_GTK_BITS) {
security->group_data_prot = 1;
}
} }
/* 2. SCIA — PMKIDs */ /* 2. SCIA — PMKIDs */
@@ -1762,6 +2405,33 @@ void nan_security_apply_pending(struct ndl_info *ndl,
(s_pending_scia.has_csid || s_pending_scia.has_pmkid)) { (s_pending_scia.has_csid || s_pending_scia.has_pmkid)) {
if (s_pending_scia.has_csid) { if (s_pending_scia.has_csid) {
/* GTK is exchanged only if both sides support it: our service must
* enable group_data_prot AND the peer must have signalled group-data
* support in its NDP-Request CSIA (Capabilities byte for iOS, or an
* NCS-GTK cipher suite for Android/ESP — captured in peer_group_data
* by esp_nan_parse_ndp_csia). */
ndl->gtk_required = (p_own_svc && p_own_svc->user_cfg.group_data_prot &&
s_pending_scia.peer_group_data) ? 1 : 0;
if (p_own_svc && p_own_svc->user_cfg.group_data_prot) {
ESP_LOGI(TAG, "NDP GTK: %s (responder) - own group_prot=1, peer group_data=%d",
ndl->gtk_required ? "negotiated" : "NOT negotiated",
s_pending_scia.peer_group_data);
}
/* IGTK distributed in M4 iff we enabled group_mgmt_prot AND the peer
* advertised IGTKSA; BIGTK additionally requires BIGTKSA (§7.1.3.5). */
{
bool own_mgmt = s_nan_ctx.group_mgmt_prot;
uint8_t grp = s_pending_scia.peer_caps & NAN_CSIA_CAP_GTK_SUPP_MASK;
bool peer_igtk = grp != 0;
bool peer_bigtk = grp == (NAN_CSIA_CAP_GTK_SUPP_ALL << NAN_CSIA_CAP_GTK_SUPP_POS);
ndl->igtk_required = (own_mgmt && peer_igtk) ? 1 : 0;
ndl->bigtk_required = (own_mgmt && peer_bigtk) ? 1 : 0;
if (own_mgmt) {
ESP_LOGI(TAG, "NDP IGTK/BIGTK: igtk=%s bigtk=%s (responder) - peer caps=0x%02x",
ndl->igtk_required ? "yes" : "no", ndl->bigtk_required ? "yes" : "no",
s_pending_scia.peer_caps);
}
}
ndl->security_ctx.csid_bitmap = s_pending_scia.csid_bitmap; ndl->security_ctx.csid_bitmap = s_pending_scia.csid_bitmap;
ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED; ndl->security_ctx.type = WIFI_NAN_SECURITY_ENCRYPTED;
} }
@@ -2105,14 +2775,12 @@ int esp_nan_get_ndp_confirm_shared_key_desc(uint8_t *buf, size_t buf_len, uint8_
uint8_t *p = buf; uint8_t *p = buf;
*p++ = NAN_ATTR_ID_SHARED_KEY_DESC; *p++ = NAN_ATTR_ID_SHARED_KEY_DESC;
{ uint8_t *len_field = p; /* backpatched once Key Data length is known */
uint16_t body_len = 1 + NAN_KEY_DESC_MIN_LEN; p += 2;
*p++ = body_len & 0xFF;
*p++ = (body_len >> 8) & 0xFF;
}
*p++ = ndl->publisher_id; *p++ = ndl->publisher_id;
int n = nan_build_rsna_key_descriptor(p, uint8_t *key_desc = p;
int n = nan_build_rsna_key_descriptor(key_desc,
NAN_KEY_INFO_MIC | NAN_KEY_INFO_MIC |
NAN_KEY_INFO_SECURE | NAN_KEY_INFO_SECURE |
NAN_KEY_INFO_ACK, NAN_KEY_INFO_ACK,
@@ -2124,12 +2792,19 @@ int esp_nan_get_ndp_confirm_shared_key_desc(uint8_t *buf, size_t buf_len, uint8_
return 0; return 0;
} }
n = 3 + 1 + n; /* Initiator distributes its GTK in M3 (§7.1.3.2). Needs the blob to size
* the M3 buffer with GTK headroom (ndp_confirm_get_shared_desc_len);
* falls back to pairwise-only otherwise. */
n = nan_append_own_group_kdes(ndl, key_desc, buf_len - 4);
uint16_t body_len = 1 + (uint16_t)n;
len_field[0] = body_len & 0xFF;
len_field[1] = (body_len >> 8) & 0xFF;
NAN_DATA_UNLOCK(); NAN_DATA_UNLOCK();
/* State transition to M3_SENT happens in host TX-confirm hook */ /* State transition to M3_SENT happens in host TX-confirm hook */
ESP_LOGD(TAG, "NDP M3 Key Desc: built (MIC=0, driver must call esp_nan_update_ndp_confirm_mic) for ndp_id=%d", ndp_id); ESP_LOGD(TAG, "NDP M3 Key Desc: built (key_desc=%d bytes, MIC=0; driver must call esp_nan_update_ndp_confirm_mic) for ndp_id=%d", n, ndp_id);
return n; return 3 + 1 + n;
} }
/** /**
@@ -35,6 +35,16 @@ menu "Example Configuration"
the same credential (passphrase or PMK). the same credential (passphrase or PMK).
Disable to advertise an open (unencrypted) service. Disable to advertise an open (unencrypted) service.
config EXAMPLE_NAN_GROUP_DATA_PROT
bool "Protect group-addressed datapath traffic (ND-GTK)"
depends on EXAMPLE_NAN_SECURITY_ENABLED
default y
help
Negotiate and install an ND-GTK so group-addressed (multicast/
broadcast) frames on the NAN datapath are encrypted. This is
required for IPv6 over the secured datapath (Neighbor Discovery,
MLD). Both peers must enable this for group keys to be exchanged.
choice EXAMPLE_NAN_SECURITY_METHOD choice EXAMPLE_NAN_SECURITY_METHOD
prompt "Security Method" prompt "Security Method"
depends on EXAMPLE_NAN_SECURITY_ENABLED depends on EXAMPLE_NAN_SECURITY_ENABLED
@@ -133,6 +133,9 @@ void wifi_nan_publish(void)
}; };
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED #ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
wifi_nan_discovery_security_params_t security_cfg = { wifi_nan_discovery_security_params_t security_cfg = {
#ifdef CONFIG_EXAMPLE_NAN_GROUP_DATA_PROT
.group_data_prot = 1, /* distribute/accept ND-GTK for group-addressed data */
#endif
.num_credentials = 1, .num_credentials = 1,
.creds = { .creds = {
{ {
@@ -45,6 +45,16 @@ menu "Example Configuration"
(passphrase or PMK) and sets up an encrypted NDP. (passphrase or PMK) and sets up an encrypted NDP.
Disable to discover open (unencrypted) services. Disable to discover open (unencrypted) services.
config EXAMPLE_NAN_GROUP_DATA_PROT
bool "Protect group-addressed datapath traffic (ND-GTK)"
depends on EXAMPLE_NAN_SECURITY_ENABLED
default y
help
Negotiate and install an ND-GTK so group-addressed (multicast/
broadcast) frames on the NAN datapath are encrypted. This is
required for IPv6 over the secured datapath (Neighbor Discovery,
MLD). Both peers must enable this for group keys to be exchanged.
choice EXAMPLE_NAN_SECURITY_METHOD choice EXAMPLE_NAN_SECURITY_METHOD
prompt "Security Method" prompt "Security Method"
depends on EXAMPLE_NAN_SECURITY_ENABLED depends on EXAMPLE_NAN_SECURITY_ENABLED
@@ -227,6 +227,9 @@ void wifi_nan_subscribe(void)
}; };
#ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED #ifdef CONFIG_EXAMPLE_NAN_SECURITY_ENABLED
wifi_nan_discovery_security_params_t security_cfg = { wifi_nan_discovery_security_params_t security_cfg = {
#ifdef CONFIG_EXAMPLE_NAN_GROUP_DATA_PROT
.group_data_prot = 1, /* distribute/accept ND-GTK for group-addressed data */
#endif
.num_credentials = 1, .num_credentials = 1,
.creds = { .creds = {
{ {