Merge branch 'feat/nan_gtk_support' into 'master'

feat(wifi_aware): advertise group data/mgmt protection (GTK/IGTK/BIGTK) and iOS compliance fixes

See merge request espressif/esp-idf!49800
This commit is contained in:
Jiang Jiang Jian
2026-07-02 18:32:56 +08:00
18 changed files with 1265 additions and 126 deletions
+25 -11
View File
@@ -67,8 +67,8 @@ typedef struct {
uint16_t csid_bitmap; /**< Selected Cipher Suite ID bit (WIFI_NAN_CSID_BIT_*) */ uint16_t csid_bitmap; /**< Selected Cipher Suite ID bit (WIFI_NAN_CSID_BIT_*) */
uint8_t nd_pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< ND-PMK */ uint8_t nd_pmk[ESP_WIFI_NAN_NDP_PMK_LEN]; /**< ND-PMK */
uint8_t nd_pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN]; /**< ND-PMKID */ uint8_t nd_pmkid[ESP_WIFI_NAN_NDP_PMKID_LEN]; /**< ND-PMKID */
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */ uint8_t group_data_prot: 1; /**< Group addressed data frame protection (GTKSA): distribute a GTK on the secured NDP so multicast data frames are protected. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */ uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection (IGTKSA/BIGTKSA): BIP-protect multicast SDFs and Beacons. */
uint8_t reserved: 6; /**< Reserved */ uint8_t reserved: 6; /**< Reserved */
} wifi_nan_security_params_t; } wifi_nan_security_params_t;
@@ -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);
}; };
/** /**
@@ -1195,7 +1209,7 @@ esp_err_t esp_wifi_disconnect_internal(void);
uint32_t esp_nan_get_nira_len(void); uint32_t esp_nan_get_nira_len(void);
/** /**
* @brief Construct dummy NAN Identity Resolution Attribute (NIRA) * @brief Construct NAN Identity Resolution Attribute (NIRA)
* *
* @param[out] frm Buffer to write the attribute to * @param[out] frm Buffer to write the attribute to
* *
@@ -81,6 +81,42 @@ bool esp_wifi_is_if_ready_when_started(wifi_netif_driver_t ifx);
*/ */
esp_err_t esp_wifi_register_if_rxcb(wifi_netif_driver_t ifx, esp_netif_receive_t fn, void * arg); esp_err_t esp_wifi_register_if_rxcb(wifi_netif_driver_t ifx, esp_netif_receive_t fn, void * arg);
/**
* @brief Derive an IPv6 link-local address from a link-layer (MAC) address
*
* Computes fe80::/64 combined with the EUI-64 form of the given MAC (the 802
* group bit complemented) into an esp_ip6_addr_t (zone 0). Interface-agnostic.
*
* @param[out] ip6 destination, set to the derived IPv6 link-local address
* @param[in] mac source link-layer (MAC) address (6 bytes)
*/
void esp_wifi_netif_get_ip6_linklocal_from_mac(esp_ip6_addr_t *ip6, const uint8_t mac[6]);
#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES
/**
* @brief Pin (or remove) a static IPv6 link-local neighbor mapping on a wifi netif
*
* Installs a fixed link-local IPv6 -> MAC mapping for a peer reachable on the
* given wifi interface so that traffic to the peer bypasses Neighbor Discovery
* (no NS/NA exchanged), or removes a previously installed one. This layer owns
* both the netif lookup (from the interface type) and the derivation of the
* peer's link-local address from its MAC, so the caller only supplies the
* interface and the peer MAC. Only available when lwIP static ND6 entries are
* enabled.
*
* @param[in] wifi_if wifi interface the peer is reachable on
* @param[in] mac peer's link-layer (MAC) address
* @param[in] add true to add the mapping, false to remove it
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if mac is NULL or wifi_if is out of range
* - ESP_ERR_INVALID_STATE if the interface's netif is not up
* - error code from the underlying esp_netif call otherwise
*/
esp_err_t esp_wifi_netif_set_static_neighbor(wifi_interface_t wifi_if, const uint8_t mac[6], bool add);
#endif /* CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES */
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
@@ -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;
/** /**
@@ -932,9 +933,9 @@ typedef enum {
WIFI_NAN_CSID_NCS_SK_256 = 2, /**< NCS-SK-256 (PSK/Passphrase). Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_SK_256 = 2, /**< NCS-SK-256 (PSK/Passphrase). Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_128 = 3, /**< NCS-PK-2WDH-128. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_2WDH_128 = 3, /**< NCS-PK-2WDH-128. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_256 = 4, /**< NCS-PK-2WDH-256. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_2WDH_256 = 4, /**< NCS-PK-2WDH-256. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_GTK_CCM_128 = 5, WIFI_NAN_CSID_NCS_GTK_CCMP_128 = 5, /**< NCS-GTK-CCMP-128, the group-data cipher (GTKSA). Selected internally when group_data_prot is set; not user-selectable via csid_bitmap. */
WIFI_NAN_CSID_NCS_GTK_GCM_256 = 6, WIFI_NAN_CSID_NCS_GTK_GCMP_256 = 6, /**< NCS-GTK-GCMP-256. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_PASN_128 = 7, /**< NCS-PK-PASN-128. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_PASN_128 = 7, /**< NCS-PK-PASN-128 (NAN Pairing). Requires CONFIG_ESP_WIFI_NAN_PAIRING and the Wi-Fi Aware component (esp-wifi-apps); not usable with stand-alone ESP-IDF. */
WIFI_NAN_CSID_NCS_PK_PASN_256 = 8, /**< NCS-PK-PASN-256. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_PASN_256 = 8, /**< NCS-PK-PASN-256. Reserved: not supported right now. */
} wifi_nan_cipher_suite_id_t; } wifi_nan_cipher_suite_id_t;
@@ -942,8 +943,8 @@ typedef enum {
#define WIFI_NAN_CSID_BIT_NCS_SK_256 (1 << WIFI_NAN_CSID_NCS_SK_256) #define WIFI_NAN_CSID_BIT_NCS_SK_256 (1 << WIFI_NAN_CSID_NCS_SK_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_128 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_128) #define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_128 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_256 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_256) #define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_256 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_256)
#define WIFI_NAN_CSID_BIT_NCS_GTK_CCM_128 (1 << WIFI_NAN_CSID_NCS_GTK_CCM_128) #define WIFI_NAN_CSID_BIT_NCS_GTK_CCMP_128 (1 << WIFI_NAN_CSID_NCS_GTK_CCMP_128)
#define WIFI_NAN_CSID_BIT_NCS_GTK_GCM_256 (1 << WIFI_NAN_CSID_NCS_GTK_GCM_256) #define WIFI_NAN_CSID_BIT_NCS_GTK_GCMP_256 (1 << WIFI_NAN_CSID_NCS_GTK_GCMP_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_128 (1 << WIFI_NAN_CSID_NCS_PK_PASN_128) #define WIFI_NAN_CSID_BIT_NCS_PK_PASN_128 (1 << WIFI_NAN_CSID_NCS_PK_PASN_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256) #define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256)
@@ -974,8 +975,8 @@ typedef struct {
* is computed by the stack as the union of each credential's @c csid. * is computed by the stack as the union of each credential's @c csid.
*/ */
typedef struct { typedef struct {
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */ uint8_t group_data_prot: 1; /**< Group addressed data frame protection (GTKSA): distribute a GTK on the secured NDP so multicast data frames are protected. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */ uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection (IGTKSA/BIGTKSA): BIP-protect multicast SDFs and Beacons. */
uint8_t reserved: 6; /**< Reserved */ uint8_t reserved: 6; /**< Reserved */
uint8_t num_credentials; /**< Number of valid entries in @c creds (0..ESP_WIFI_NAN_MAX_CREDS_PER_SVC). 0 = open service. */ uint8_t num_credentials; /**< Number of valid entries in @c creds (0..ESP_WIFI_NAN_MAX_CREDS_PER_SVC). 0 = open service. */
wifi_nan_credential_t creds[ESP_WIFI_NAN_MAX_CREDS_PER_SVC]; /**< Credentials list. */ wifi_nan_credential_t creds[ESP_WIFI_NAN_MAX_CREDS_PER_SVC]; /**< Credentials list. */
@@ -81,6 +81,42 @@ bool esp_wifi_is_if_ready_when_started(wifi_netif_driver_t ifx);
*/ */
esp_err_t esp_wifi_register_if_rxcb(wifi_netif_driver_t ifx, esp_netif_receive_t fn, void * arg); esp_err_t esp_wifi_register_if_rxcb(wifi_netif_driver_t ifx, esp_netif_receive_t fn, void * arg);
/**
* @brief Derive an IPv6 link-local address from a link-layer (MAC) address
*
* Computes fe80::/64 combined with the EUI-64 form of the given MAC (the 802
* group bit complemented) into an esp_ip6_addr_t (zone 0). Interface-agnostic.
*
* @param[out] ip6 destination, set to the derived IPv6 link-local address
* @param[in] mac source link-layer (MAC) address (6 bytes)
*/
void esp_wifi_netif_get_ip6_linklocal_from_mac(esp_ip6_addr_t *ip6, const uint8_t mac[6]);
#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES
/**
* @brief Pin (or remove) a static IPv6 link-local neighbor mapping on a wifi netif
*
* Installs a fixed link-local IPv6 -> MAC mapping for a peer reachable on the
* given wifi interface so that traffic to the peer bypasses Neighbor Discovery
* (no NS/NA exchanged), or removes a previously installed one. This layer owns
* both the netif lookup (from the interface type) and the derivation of the
* peer's link-local address from its MAC, so the caller only supplies the
* interface and the peer MAC. Only available when lwIP static ND6 entries are
* enabled.
*
* @param[in] wifi_if wifi interface the peer is reachable on
* @param[in] mac peer's link-layer (MAC) address
* @param[in] add true to add the mapping, false to remove it
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if mac is NULL or wifi_if is out of range
* - ESP_ERR_INVALID_STATE if the interface's netif is not up
* - error code from the underlying esp_netif call otherwise
*/
esp_err_t esp_wifi_netif_set_static_neighbor(wifi_interface_t wifi_if, const uint8_t mac[6], bool add);
#endif /* CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES */
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
@@ -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;
/** /**
@@ -932,9 +933,9 @@ typedef enum {
WIFI_NAN_CSID_NCS_SK_256 = 2, /**< NCS-SK-256 (PSK/Passphrase). Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_SK_256 = 2, /**< NCS-SK-256 (PSK/Passphrase). Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_128 = 3, /**< NCS-PK-2WDH-128. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_2WDH_128 = 3, /**< NCS-PK-2WDH-128. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_2WDH_256 = 4, /**< NCS-PK-2WDH-256. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_2WDH_256 = 4, /**< NCS-PK-2WDH-256. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_GTK_CCM_128 = 5, WIFI_NAN_CSID_NCS_GTK_CCMP_128 = 5, /**< NCS-GTK-CCMP-128, the group-data cipher (GTKSA). Selected internally when group_data_prot is set; not user-selectable via csid_bitmap. */
WIFI_NAN_CSID_NCS_GTK_GCM_256 = 6, WIFI_NAN_CSID_NCS_GTK_GCMP_256 = 6, /**< NCS-GTK-GCMP-256. Reserved: not supported right now. */
WIFI_NAN_CSID_NCS_PK_PASN_128 = 7, /**< NCS-PK-PASN-128. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_PASN_128 = 7, /**< NCS-PK-PASN-128 (NAN Pairing). Requires CONFIG_WIFI_RMT_NAN_PAIRING and the Wi-Fi Aware component (esp-wifi-apps); not usable with stand-alone ESP-IDF. */
WIFI_NAN_CSID_NCS_PK_PASN_256 = 8, /**< NCS-PK-PASN-256. Reserved: not supported right now. */ WIFI_NAN_CSID_NCS_PK_PASN_256 = 8, /**< NCS-PK-PASN-256. Reserved: not supported right now. */
} wifi_nan_cipher_suite_id_t; } wifi_nan_cipher_suite_id_t;
@@ -942,8 +943,8 @@ typedef enum {
#define WIFI_NAN_CSID_BIT_NCS_SK_256 (1 << WIFI_NAN_CSID_NCS_SK_256) #define WIFI_NAN_CSID_BIT_NCS_SK_256 (1 << WIFI_NAN_CSID_NCS_SK_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_128 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_128) #define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_128 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_256 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_256) #define WIFI_NAN_CSID_BIT_NCS_PK_2WDH_256 (1 << WIFI_NAN_CSID_NCS_PK_2WDH_256)
#define WIFI_NAN_CSID_BIT_NCS_GTK_CCM_128 (1 << WIFI_NAN_CSID_NCS_GTK_CCM_128) #define WIFI_NAN_CSID_BIT_NCS_GTK_CCMP_128 (1 << WIFI_NAN_CSID_NCS_GTK_CCMP_128)
#define WIFI_NAN_CSID_BIT_NCS_GTK_GCM_256 (1 << WIFI_NAN_CSID_NCS_GTK_GCM_256) #define WIFI_NAN_CSID_BIT_NCS_GTK_GCMP_256 (1 << WIFI_NAN_CSID_NCS_GTK_GCMP_256)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_128 (1 << WIFI_NAN_CSID_NCS_PK_PASN_128) #define WIFI_NAN_CSID_BIT_NCS_PK_PASN_128 (1 << WIFI_NAN_CSID_NCS_PK_PASN_128)
#define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256) #define WIFI_NAN_CSID_BIT_NCS_PK_PASN_256 (1 << WIFI_NAN_CSID_NCS_PK_PASN_256)
@@ -974,8 +975,8 @@ typedef struct {
* is computed by the stack as the union of each credential's @c csid. * is computed by the stack as the union of each credential's @c csid.
*/ */
typedef struct { typedef struct {
uint8_t group_data_prot: 1; /**< Group addressed data frame protection. Reserved: not supported right now. */ uint8_t group_data_prot: 1; /**< Group addressed data frame protection (GTKSA): distribute a GTK on the secured NDP so multicast data frames are protected. */
uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection. Reserved: not supported right now. */ uint8_t group_mgmt_prot: 1; /**< Group addressed management frame protection (IGTKSA/BIGTKSA): BIP-protect multicast SDFs and Beacons. */
uint8_t reserved: 6; /**< Reserved */ uint8_t reserved: 6; /**< Reserved */
uint8_t num_credentials; /**< Number of valid entries in @c creds (0..ESP_WIFI_NAN_MAX_CREDS_PER_SVC). 0 = open service. */ uint8_t num_credentials; /**< Number of valid entries in @c creds (0..ESP_WIFI_NAN_MAX_CREDS_PER_SVC). 0 = open service. */
wifi_nan_credential_t creds[ESP_WIFI_NAN_MAX_CREDS_PER_SVC]; /**< Credentials list. */ wifi_nan_credential_t creds[ESP_WIFI_NAN_MAX_CREDS_PER_SVC]; /**< Credentials list. */
+6 -1
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -184,6 +184,11 @@ static void wifi_default_action_nan_started(void *arg, esp_event_base_t base, in
if (s_wifi_netifs[WIFI_IF_NAN] != NULL) { if (s_wifi_netifs[WIFI_IF_NAN] != NULL) {
wifi_start(s_wifi_netifs[WIFI_IF_NAN], base, event_id, data); wifi_start(s_wifi_netifs[WIFI_IF_NAN], base, event_id, data);
esp_nan_action_start(s_wifi_netifs[WIFI_IF_NAN]); esp_nan_action_start(s_wifi_netifs[WIFI_IF_NAN]);
/* Bring the netif up before esp_netif_create_ip6_linklocal() (a no-op unless
* netif_is_up()). esp_netif_up() is private, so use the public action handler;
* NAN is non-DHCP, so it only calls esp_netif_up() and ignores the event args. */
esp_netif_action_connected(s_wifi_netifs[WIFI_IF_NAN], NULL, 0, NULL);
esp_netif_create_ip6_linklocal(s_wifi_netifs[WIFI_IF_NAN]);
} }
} }
+43
View File
@@ -3,6 +3,7 @@
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#include <string.h>
#include "esp_wifi.h" #include "esp_wifi.h"
#include "esp_netif.h" #include "esp_netif.h"
#include "esp_log.h" #include "esp_log.h"
@@ -181,3 +182,45 @@ esp_err_t esp_wifi_register_if_rxcb(wifi_netif_driver_t ifx, esp_netif_receive_t
} }
return ESP_OK; return ESP_OK;
} }
void esp_wifi_netif_get_ip6_linklocal_from_mac(esp_ip6_addr_t *ip6, const uint8_t mac[6])
{
if (ip6 == NULL || mac == NULL) {
return;
}
/* fe80::/64 + EUI-64 of the MAC (802 group bit complemented), laid out in
* network byte order straight into esp_ip6_addr_t. Zone stays 0. */
const uint8_t linklocal[16] = {
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
(uint8_t)(mac[0] ^ 0x02), mac[1], mac[2], 0xff,
0xfe, mac[3], mac[4], mac[5],
};
memset(ip6, 0, sizeof(*ip6));
memcpy(ip6->addr, linklocal, sizeof(linklocal));
}
#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES
esp_err_t esp_wifi_netif_set_static_neighbor(wifi_interface_t wifi_if, const uint8_t mac[6], bool add)
{
if (mac == NULL || wifi_if >= MAX_WIFI_IFS) {
return ESP_ERR_INVALID_ARG;
}
/* The netif handle is owned by this layer (recorded when the interface's RX
* callback is registered), so callers only need to supply the interface and
* the peer MAC. */
esp_netif_t *esp_netif = s_wifi_netifs[wifi_if];
if (esp_netif == NULL) {
return ESP_ERR_INVALID_STATE;
}
/* Derive the peer's link-local address; esp_netif copies only the address
* words for static neighbor entries, so no zone handling is needed here. */
esp_ip6_addr_t addr6;
esp_wifi_netif_get_ip6_linklocal_from_mac(&addr6, mac);
return add ? esp_netif_add_static_neighbor(esp_netif, &addr6, mac)
: esp_netif_remove_static_neighbor(esp_netif, &addr6);
}
#endif /* CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES */
@@ -24,6 +24,7 @@ extern "C" {
.disable_random_mac = false, \ .disable_random_mac = false, \
.reset_current_nvs_creds = false, \ .reset_current_nvs_creds = false, \
.use_nvs_for_caching = false, \ .use_nvs_for_caching = false, \
.group_mgmt_prot = false, \
}; };
#define NDP_STATUS_ACCEPTED 1 #define NDP_STATUS_ACCEPTED 1
@@ -264,13 +264,48 @@ 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);
/* Drop the peer's RX IGTK/BIGTK (BIP-CMAC-128, installed against the
* peer NMI at NDP confirm) so no stale BIP keys linger in the blob. */
if (ndl->igtk_set) {
esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_BIP_CMAC_128,
(uint8_t *)peer_nmi, ndl->igtk_keyid, 0,
key_rsc, 6,
NULL, 0, NAN_KEY_ND_IGTK);
}
if (ndl->bigtk_set) {
esp_wifi_set_nan_key_internal(NAN_WIFI_WPA_ALG_BIP_CMAC_128,
(uint8_t *)peer_nmi, ndl->bigtk_keyid, 0,
key_rsc, 6,
NULL, 0, NAN_KEY_ND_BIGTK);
}
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));
forced_memzero(ndl->igtk, sizeof(ndl->igtk));
forced_memzero(ndl->bigtk, sizeof(ndl->bigtk));
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;
ndl->igtk_set = 0;
ndl->bigtk_set = 0;
ndl->igtk_len = 0;
ndl->bigtk_len = 0;
} }
/* Fallback when no NDL slot tracks peer_ndi yet. */ /* Fallback when no NDL slot tracks peer_ndi yet. */
@@ -337,20 +372,12 @@ void esp_wifi_nan_get_ipv6_linklocal_from_mac(ip6_addr_t *ip6, uint8_t *mac_addr
if (ip6 == NULL || mac_addr == NULL) { if (ip6 == NULL || mac_addr == NULL) {
return; return;
} }
/* Link-local prefix. */ /* Reuse the interface-agnostic derivation in the esp_wifi netif layer, then
ip6->addr[0] = htonl(0xfe800000ul); * copy the address words into the lwIP ip6_addr_t. The two structures share
ip6->addr[1] = 0; * the same layout, which is how esp_netif converts between them. */
esp_ip6_addr_t esp_ip6;
/* Assume hwaddr is a 48-bit IEEE 802 MAC. Convert to EUI-64 address. Complement Group bit. */ esp_wifi_netif_get_ip6_linklocal_from_mac(&esp_ip6, mac_addr);
ip6->addr[2] = htonl((((uint32_t)(mac_addr[0] ^ 0x02)) << 24) | memcpy(ip6->addr, esp_ip6.addr, sizeof(ip6->addr));
((uint32_t)(mac_addr[1]) << 16) |
((uint32_t)(mac_addr[2]) << 8) |
(0xff));
ip6->addr[3] = htonl((uint32_t)(0xfeul << 24) |
((uint32_t)(mac_addr[3]) << 16) |
((uint32_t)(mac_addr[4]) << 8) |
(mac_addr[5]));
ip6->zone = IP6_NO_ZONE; ip6->zone = IP6_NO_ZONE;
} }
@@ -601,6 +628,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 +720,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 +1064,6 @@ 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);
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;
@@ -1036,8 +1075,8 @@ static void nan_app_replied_cb(uint8_t pub_id, struct nan_cb_peer_info *peer_inf
} }
static void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_info, static void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_info,
uint8_t *shared_key_attr, uint16_t shared_key_attr_buf_len, uint8_t *shared_key_attr, uint16_t shared_key_attr_buf_len,
struct nan_cb_npba_t *npba) struct nan_cb_npba_t *npba)
{ {
if (!peer_info) { if (!peer_info) {
return; return;
@@ -1147,8 +1186,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);
@@ -1176,8 +1218,13 @@ static void nan_app_ndp_indication_cb(uint8_t pub_id, struct ndp_cb_peer_info *p
uint8_t own_bssid[6]; uint8_t own_bssid[6];
esp_err_t err = esp_wifi_get_mac(WIFI_IF_NAN, own_bssid); esp_err_t err = esp_wifi_get_mac(WIFI_IF_NAN, own_bssid);
if (err != ESP_OK) { if (err != ESP_OK) {
/* Cannot build the auto-response: free the NDL slot and deny the
* peer so it does not wait indefinitely. Send outside the lock. */
ESP_LOGE(TAG, "get own NAN MAC failed, rc=0x%x; denying NDP ndp_id=%d", err, ndp_id);
nan_reset_ndl(ndp_id, false);
NAN_DATA_UNLOCK(); NAN_DATA_UNLOCK();
ESP_LOGE(TAG, "Cannot get own BSSID!"); ndp_resp.accept = false;
esp_nan_internal_datapath_resp(&ndp_resp, (uint8_t *)&own_ipv6.u_addr.ip6.addr[2]);
return; return;
} }
esp_wifi_nan_get_ipv6_linklocal_from_mac(&own_ipv6.u_addr.ip6, own_bssid); esp_wifi_nan_get_ipv6_linklocal_from_mac(&own_ipv6.u_addr.ip6, own_bssid);
@@ -1359,10 +1406,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 +1418,103 @@ 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_DEBUG);
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 ND-TK (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;
} }
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) {
ESP_LOGE(TAG, "NDP confirm: failed to install NM-TK (ndp_id=%d, ret=%d)", ndp_id, ret);
os_free(evt);
nan_ndp_confirm_teardown(peer_nmi, ndp_id);
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_DEBUG);
}
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, seeding the BIP
* RX replay counter with the peer's advertised IPN/BIPN from the KDE. */
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,
ndl->igtk_ipn, 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_DEBUG);
}
}
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,
ndl->bigtk_ipn, 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_DEBUG);
}
}
} }
#endif /* CONFIG_ESP_WIFI_NAN_SECURITY */ #endif /* CONFIG_ESP_WIFI_NAN_SECURITY */
evt->status = status; evt->status = status;
@@ -1402,8 +1536,6 @@ static void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer
ESP_LOG_BUFFER_HEXDUMP(TAG, ssi, ssi_len, ESP_LOG_DEBUG); ESP_LOG_BUFFER_HEXDUMP(TAG, ssi, ssi_len, ESP_LOG_DEBUG);
} }
esp_netif_action_connected(s_nan_ctx.nan_netif, WIFI_EVENT, WIFI_EVENT_NDP_CONFIRM, evt);
esp_netif_create_ip6_linklocal(s_nan_ctx.nan_netif);
NAN_DATA_UNLOCK(); NAN_DATA_UNLOCK();
ip6_addr_t peer_ip6 = {0}; ip6_addr_t peer_ip6 = {0};
@@ -1414,6 +1546,20 @@ static void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer
ESP_LOGI(TAG, "NDP confirmed with Peer "MACSTR" [NDP ID - %d, Peer IPv6 - %s]", ESP_LOGI(TAG, "NDP confirmed with Peer "MACSTR" [NDP ID - %d, Peer IPv6 - %s]",
MAC2STR(peer_nmi), ndp_id, inet6_ntoa(peer_ip6)); MAC2STR(peer_nmi), ndp_id, inet6_ntoa(peer_ip6));
#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES
/* Pin the peer's link-local -> NDI mapping so traffic to the peer skips
* Neighbor Discovery (no NS/NA) on the NAN link. The esp_wifi netif layer
* owns the netif lookup and derives the peer's link-local from its NDI
* (the address the peer actually sources from). */
esp_err_t nbr_err = esp_wifi_netif_set_static_neighbor(WIFI_IF_NAN, peer_ndi, true);
if (nbr_err != ESP_OK) {
ESP_LOGW(TAG, "static nbr ADD failed: %s", esp_err_to_name(nbr_err));
}
#else
esp_netif_action_connected(s_nan_ctx.nan_netif, WIFI_EVENT, WIFI_EVENT_NDP_CONFIRM, evt);
esp_netif_create_ip6_linklocal(s_nan_ctx.nan_netif);
#endif
os_event_group_set_bits(nan_event_group, NDP_ACCEPTED); os_event_group_set_bits(nan_event_group, NDP_ACCEPTED);
nan_app_post_event(WIFI_EVENT_NDP_CONFIRM, evt, evt_data_len); nan_app_post_event(WIFI_EVENT_NDP_CONFIRM, evt, evt_data_len);
os_free(evt); os_free(evt);
@@ -1436,6 +1582,15 @@ static void nan_app_ndp_terminated_cb(uint8_t reason, uint8_t ndp_id, uint8_t in
s_nan_ctx.event &= ~(NDP_INDICATION); s_nan_ctx.event &= ~(NDP_INDICATION);
NAN_DATA_UNLOCK(); NAN_DATA_UNLOCK();
#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES
/* Drop the peer's static neighbor mapping added on NDP confirm. (It is also
* cleared automatically if the NAN netif goes down on the last datapath.) */
esp_err_t nbr_err = esp_wifi_netif_set_static_neighbor(WIFI_IF_NAN, init_ndi, false);
if (nbr_err != ESP_OK) {
ESP_LOGW(TAG, "static nbr DEL failed: %s", esp_err_to_name(nbr_err));
}
#endif
wifi_event_ndp_terminated_t *evt = (wifi_event_ndp_terminated_t *)os_zalloc(sizeof(wifi_event_ndp_terminated_t)); wifi_event_ndp_terminated_t *evt = (wifi_event_ndp_terminated_t *)os_zalloc(sizeof(wifi_event_ndp_terminated_t));
if (!evt) { if (!evt) {
ESP_LOGE(TAG, "Failed to allocate for event"); ESP_LOGE(TAG, "Failed to allocate for event");
@@ -1506,6 +1661,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 +1842,16 @@ 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
/* s_nan_ctx.group_mgmt_prot (device-global IGTKSA/BIGTKSA, one per NMI) was
* captured from the user's start config in esp_wifi_nan_sync_start().
* 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);
@@ -1713,6 +1879,13 @@ void esp_nan_action_stop(void)
nan_app_clear_paired_peers(); nan_app_clear_paired_peers();
#endif #endif
#ifdef CONFIG_ESP_WIFI_NAN_SECURITY
/* Drop the device-global IGTK/BIGTK so the next start regenerates fresh
* keys instead of re-installing a stale key with IPN/BIPN=0 (which would
* reset the blob's replay counter) — see nan_security_reset_own_group_keys. */
nan_security_reset_own_group_keys();
#endif
esp_nan_internal_register_callbacks(NULL); esp_nan_internal_register_callbacks(NULL);
os_event_group_clear_bits(nan_event_group, NAN_STARTED_BIT); os_event_group_clear_bits(nan_event_group, NAN_STARTED_BIT);
os_event_group_set_bits(nan_event_group, NAN_STOPPED_BIT); os_event_group_set_bits(nan_event_group, NAN_STOPPED_BIT);
@@ -1756,6 +1929,7 @@ esp_err_t esp_wifi_nan_sync_start(const wifi_nan_sync_config_t *nan_cfg)
s_nan_ctx.num_peer_creds = 0; s_nan_ctx.num_peer_creds = 0;
memset(s_nan_ctx.peer_creds, 0, sizeof(s_nan_ctx.peer_creds)); memset(s_nan_ctx.peer_creds, 0, sizeof(s_nan_ctx.peer_creds));
s_nan_ctx.use_nvs_for_caching = nan_cfg->use_nvs_for_caching; s_nan_ctx.use_nvs_for_caching = nan_cfg->use_nvs_for_caching;
s_nan_ctx.group_mgmt_prot = nan_cfg->group_mgmt_prot;
s_nan_ctx.nik_lifetime = 0; s_nan_ctx.nik_lifetime = 0;
if (nan_cfg->reset_current_nvs_creds) { if (nan_cfg->reset_current_nvs_creds) {
@@ -2525,7 +2699,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,40 @@ 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 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 +172,16 @@ 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; 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 (nan_key_type_t: NAN_KEY_ND_TK / ND_GTK / NM_TK / ND_IGTK /
* ND_BIGTK), passed as the last arg of esp_wifi_set_nan_key_internal, is defined in
* esp_wifi_driver.h (included above) and shared with the blob. */
/* Handshake state */ /* Handshake state */
enum nan_handshake_state { enum nan_handshake_state {
NAN_HANDSHAKE_IDLE = 0, NAN_HANDSHAKE_IDLE = 0,
@@ -195,6 +234,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 +319,38 @@ 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 igtk_ipn[6]; /* peer IGTK IPN (seeds BIP RX replay counter) */
uint8_t bigtk_ipn[6]; /* peer BIGTK BIPN (seeds BIP RX replay counter) */
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 +368,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 +426,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 +515,17 @@ 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);
/* Clear the device-global IGTK/BIGTK state on NAN stop so the next NAN start
* regenerates fresh keys. Prevents re-installing a stale key with IPN/BIPN=0
* (which resets the blob's monotonic replay counter) and key reuse if the NMI
* is re-randomized on restart. */
void nan_security_reset_own_group_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 CCMP-128. */
#define NAN_CSID_GTK_BITS (WIFI_NAN_CSID_BIT_NCS_GTK_CCMP_128 | \
WIFI_NAN_CSID_BIT_NCS_GTK_GCMP_256)
#define NAN_CSID_GTK_DEFAULT WIFI_NAN_CSID_BIT_NCS_GTK_CCMP_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,357 @@ 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().
*-----------------------------------------------------------------------*/
/* 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_DEBUG);
}
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_DEBUG);
}
}
void nan_security_reset_own_group_keys(void)
{
forced_memzero(s_nan_ctx.own_igtk, sizeof(s_nan_ctx.own_igtk));
memset(s_nan_ctx.own_igtk_ipn, 0, sizeof(s_nan_ctx.own_igtk_ipn));
s_nan_ctx.own_igtk_keyid = 0;
s_nan_ctx.own_igtk_set = false;
forced_memzero(s_nan_ctx.own_bigtk, sizeof(s_nan_ctx.own_bigtk));
memset(s_nan_ctx.own_bigtk_bipn, 0, sizeof(s_nan_ctx.own_bigtk_bipn));
s_nan_ctx.own_bigtk_keyid = 0;
s_nan_ctx.own_bigtk_set = false;
}
/* 00-0F-AC RSN OUI written into every NAN KDE header. */
static const uint8_t nan_kde_rsn_oui[3] = {0x00, 0x0f, 0xac};
/* 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 */
memcpy(p, nan_kde_rsn_oui, sizeof(nan_kde_rsn_oui));
p += sizeof(nan_kde_rsn_oui);
*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 < 1 || ndl->own_gtk_keyid > 2) { /* §7.1.3.2: GTK Key ID is 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);
}
int rc = aes_wrap(ndl->nd_kek, ndl->kek_len, (int)(padded / 8), pad, out);
forced_memzero(pad, sizeof(pad)); /* scrub plaintext group keys */
if (rc != 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);
size_t plain_len = 0;
size_t wrapped_len = 0;
uint16_t key_info = 0;
int ret = NAN_KEY_DESC_MIN_LEN;
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 " : "");
goto out;
}
plain_len = (size_t)(p - plain);
if (plain_len == 0) {
goto out; /* nothing negotiated to send */
}
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 " : "");
goto out;
}
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 only when a GTK KDE is present (§7.1.3.5). */
if (want_gtk) {
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 " : "");
ret = NAN_KEY_DESC_MIN_LEN + (int)wrapped_len;
out:
forced_memzero(plain, sizeof(plain)); /* scrub assembled plaintext group keys */
return ret;
}
/* /*
* 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 +1024,8 @@ 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);
return written;
} }
/* /*
@@ -871,10 +1260,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-CCMP-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 +1316,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.
* 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 +1346,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 +1543,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 +1608,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 +1638,8 @@ 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);
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 +1703,6 @@ 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);
return len; return len;
} }
@@ -1256,9 +1729,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 +1953,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 +1970,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 +2037,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,57 +2211,106 @@ 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;
}
} else {
/* §7.1.3.5 / 802.11 §12.7.2: group KDEs are only ever carried
* KEK-wrapped; ignore any Key Data presented in the clear. */
ESP_LOGW(TAG, "NDP Key Desc: Key Data not encrypted; ignoring KDEs");
kde_len = 0;
}
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_ipn, body + 2, 6); /* IPN follows KeyID(2) */
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_ipn, body + 2, 6); /* BIPN follows KeyID(2) */
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;
} }
forced_memzero(plain, sizeof(plain)); /* scrub decrypted group keys */
} }
} else if (msg_type == 1) { } else if (msg_type == 1) {
/* M1 received but NDL not created yet — store as pending */ /* M1 received but NDL not created yet — store as pending */
@@ -1698,7 +2346,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 +2370,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 +2428,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 +2798,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 +2815,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;
} }
/** /**
@@ -238,6 +238,8 @@ typedef enum {
NAN_KEY_ND_TK = 0, NAN_KEY_ND_TK = 0,
NAN_KEY_ND_GTK, NAN_KEY_ND_GTK,
NAN_KEY_NM_TK, NAN_KEY_NM_TK,
NAN_KEY_ND_IGTK, /* 3 - NAN Integrity Group Temporal Key (BIP-CMAC-128) */
NAN_KEY_ND_BIGTK, /* 4 - NAN Beacon Integrity Group Temporal Key (BIP-CMAC-128) */
} nan_key_type_t; } nan_key_type_t;
typedef struct { typedef struct {
@@ -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 = {
{ {