mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
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:
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
Submodule components/esp_wifi/lib updated: 5c5338ebbd...dcdac54b98
@@ -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 = {
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user