From 9f361f478de10f08d0743e801f6cbe693cd1e7f6 Mon Sep 17 00:00:00 2001 From: Nachiket Kukade Date: Tue, 19 May 2026 20:12:20 +0800 Subject: [PATCH] feat(esp_wifi): Add NAN Pairing support - Add container struct for internal extra params for follow-up - Support for parsing Shared Key Desc in Pairing follow-up - Implement NAN Pairing API's with required parameters - In KeyData set cipher_ver to 0, Key Info to 0x12C8 (AKM-defined | Pairwise | Install | ACK | Secure | Encrypted Key Data) for iOS compatibility - Move NAN PASN into esp_nan_supplicant.c, move declarations to esp_private/esp_supp_nan.h - Align PASN/ND-PMK derivation with hostap Co-authored-by: Sajia Co-authored-by: Akshat Agrawal Co-authored-by: Sarvesh Bodakhe --- components/esp_wifi/CMakeLists.txt | 3 + .../esp_wifi/include/esp_private/wifi.h | 9 +- .../esp_wifi/include/esp_private/wifi_types.h | 17 + .../esp_wifi/include/esp_wifi_types_generic.h | 35 +- .../include/injected/esp_wifi_types_generic.h | 35 +- .../wifi_apps/nan_app/include/esp_nan.h | 40 +- .../esp_wifi/wifi_apps/nan_app/src/nan_app.c | 355 ++------ .../esp_wifi/wifi_apps/nan_app/src/nan_i.h | 76 +- .../wifi_apps/nan_app/src/nan_pairing.c | 835 ++++++++++++++++++ .../wifi_apps/nan_app/src/nan_security.c | 32 +- components/wpa_supplicant/CMakeLists.txt | 2 +- .../include/esp_private/esp_supp_nan.h | 132 +++ .../esp_supplicant/include/nan_pasn.h | 143 --- .../src/crypto/crypto_mbedtls-ec.c | 4 + .../esp_supplicant/src/esp_nan_supp_i.h | 55 ++ .../src/{nan_pasn.c => esp_nan_supplicant.c} | 700 +++++++++++++-- .../esp_supplicant/src/esp_wifi_driver.h | 6 + .../wpa_supplicant/src/common/wpa_common.c | 192 ++-- .../wpa_supplicant/src/common/wpa_common.h | 12 +- .../wpa_supplicant/src/pasn/pasn_initiator.c | 15 +- .../wpa_supplicant/src/pasn/pasn_responder.c | 14 +- 21 files changed, 2065 insertions(+), 647 deletions(-) create mode 100644 components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c create mode 100644 components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h delete mode 100644 components/wpa_supplicant/esp_supplicant/include/nan_pasn.h create mode 100644 components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h rename components/wpa_supplicant/esp_supplicant/src/{nan_pasn.c => esp_nan_supplicant.c} (60%) diff --git a/components/esp_wifi/CMakeLists.txt b/components/esp_wifi/CMakeLists.txt index f9e8fb8ee57..ddda9ebb331 100644 --- a/components/esp_wifi/CMakeLists.txt +++ b/components/esp_wifi/CMakeLists.txt @@ -73,6 +73,9 @@ if(CONFIG_ESP_WIFI_ENABLED OR CONFIG_ESP_HOST_WIFI_ENABLED) if(CONFIG_ESP_WIFI_NAN_SECURITY) list(APPEND srcs "wifi_apps/nan_app/src/nan_security.c") endif() + if(CONFIG_ESP_WIFI_NAN_PAIRING) + list(APPEND srcs "wifi_apps/nan_app/src/nan_pairing.c") + endif() endif() if(CONFIG_ESP_WIFI_ENABLE_ROAMING_APP) list(APPEND srcs "wifi_apps/roaming_app/src/roaming_app.c") diff --git a/components/esp_wifi/include/esp_private/wifi.h b/components/esp_wifi/include/esp_private/wifi.h index 26490246ff7..20eca9d65b4 100644 --- a/components/esp_wifi/include/esp_private/wifi.h +++ b/components/esp_wifi/include/esp_private/wifi.h @@ -102,6 +102,7 @@ struct nan_cb_peer_info { uint16_t ssi_len; /**< SSI length in bytes */ wifi_nan_peer_sdf_security_t *peer_security_params; /**< Peer's discovery security params parsed from SDF */ nan_vendor_ie_t *vendor_ie; /**< Vendor-specific IE, if any */ + uint8_t *shared_key_attr; /**< Shared key descriptor attribute, if any */ }; /* NDP Peer info parsed from NAF. */ @@ -176,6 +177,8 @@ struct nan_sync_callbacks { void (* pairing_indication)(uint8_t peer_svc_id, uint8_t pub_id, uint8_t peer_nmi[6], uint16_t selected_method); void (* pairing_confirm)(uint8_t status, uint8_t peer_svc_id, uint8_t sub_id, uint8_t peer_nmi[6], uint16_t matched_method, uint8_t reason_code); void (* receive_pasn)(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t status); + uint32_t (* get_nira_len)(void); + int (* construct_nira)(uint8_t *frm); }; /* Host helpers for NAN encrypted-datapath, registered via @@ -1040,15 +1043,15 @@ esp_err_t esp_nan_internal_subscribe_service(const wifi_nan_subscribe_cfg_t *sub * * @param[in] fup_params Configuration parameters for sending a Follow-up to the Peer. * @param[out] context Context returned for Follow-up frame to be matched in Tx done. - * @param[in] pairing_npba Optional NPBA parameters for NAN pairing follow-up generation. - * If NULL, follow-up is sent with provided `fup_params->ssi`. + * @param[in] params_i Optional internal-only extras (NPBA / wrapped Shared Key Descriptor). + * If NULL, follow-up is sent with provided `fup_params->ssi`. * * @return * - ESP_OK: succeed * - others: failed */ esp_err_t esp_nan_internal_send_followup(wifi_nan_followup_params_t *fup_params, uint32_t *context, - wifi_nan_pairing_npba_params_t *pairing_npba); + extra_params_internal_t *params_i); /** * @brief Send Datapath Request to the Publisher with matching service diff --git a/components/esp_wifi/include/esp_private/wifi_types.h b/components/esp_wifi/include/esp_private/wifi_types.h index b30217e2c1f..3c8a792d5b3 100644 --- a/components/esp_wifi/include/esp_private/wifi_types.h +++ b/components/esp_wifi/include/esp_private/wifi_types.h @@ -109,6 +109,23 @@ typedef struct { uint32_t cookie; /**< Comeback cookie */ } wifi_nan_pairing_npba_params_t; +/** + * @brief Container for optional internal follow-up TX parameters. + * + * Aggregates everything beyond the basic @c wifi_nan_followup_params_t that + * the firmware/proprietary layer may need to compose a follow-up frame: + * NPBA attribute parameters (NAN pairing), a pre-wrapped Shared Key + * Descriptor blob (NAN PASN follow-up), and a NAN Identity Resolution + * Attribute (NIRA) blob. Any field may be NULL/zeroed when not in use. + */ +typedef struct { + wifi_nan_pairing_npba_params_t *pairing_npba; /**< NPBA params, or NULL */ + uint8_t *shared_key_wrapped; /**< Encoded Shared Key Descriptor attr, or NULL */ + uint16_t shared_key_wrapped_len; /**< Length of @c shared_key_wrapped in bytes */ + uint8_t *nira_attr; /**< Encoded NAN Identity Resolution Attribute, or NULL */ + uint16_t nira_attr_len; /**< Length of @c nira_attr in bytes */ +} extra_params_internal_t; + #ifdef __cplusplus } #endif diff --git a/components/esp_wifi/include/esp_wifi_types_generic.h b/components/esp_wifi/include/esp_wifi_types_generic.h index 137ac2eec91..1d79e0658c4 100644 --- a/components/esp_wifi/include/esp_wifi_types_generic.h +++ b/components/esp_wifi/include/esp_wifi_types_generic.h @@ -591,6 +591,8 @@ typedef struct { uint8_t sae_h2e_identifier[SAE_H2E_IDENTIFIER_LEN]; /**< Password identifier for H2E. Strings null-terminated (length < SAE_H2E_IDENTIFIER_LEN) or non-null terminated (length = SAE_H2E_IDENTIFIER_LEN) are accepted. Non-null terminated string with 0xFF for full length of SAE_H2E_IDENTIFIER_LEN is not considered a valid identifier */ } wifi_sta_config_t; +#define ESP_WIFI_NAN_NIK_LEN 16 /**< Length of NAN Identity Key (NIK) */ + /** * @brief NAN Discovery start configuration */ @@ -600,6 +602,9 @@ typedef struct { uint8_t scan_time; /**< Scan time in seconds while searching for a NAN cluster */ uint16_t warm_up_sec; /**< Warm up time before assuming NAN Anchor Master role */ bool disable_random_mac;/**< Disable the MAC Randomisation in NAN */ + uint8_t nik[ESP_WIFI_NAN_NIK_LEN]; /**< Optional NIK. Auto-generated when nik_valid is false. */ + uint8_t nik_valid: 1; /**< NIK present in nik[] and should be used as-is. */ + uint8_t reserved: 7; /**< Reserved for future use. */ } wifi_nan_sync_config_t; /** @@ -1287,8 +1292,10 @@ typedef enum { WIFI_EVENT_NDP_INDICATION, /**< Received NDP Request from a NAN Peer */ WIFI_EVENT_NDP_CONFIRM, /**< NDP Confirm Indication */ WIFI_EVENT_NDP_TERMINATED, /**< NAN Datapath terminated indication */ - WIFI_EVENT_NAN_PAIRING_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ - WIFI_EVENT_NAN_PAIRING_CONFIRM, /**< NAN Pairing Bootstrapping completed (success/failure) */ + WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ + WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, /**< NAN Pairing Bootstrapping completed (success/failure) */ + WIFI_EVENT_NAN_PAIRING_INDICATION, /**< Received NAN Pairing indication (reserved) */ + WIFI_EVENT_NAN_PAIRING_CONFIRM, /**< NAN PASN pairwise key installation completed */ WIFI_EVENT_HOME_CHANNEL_CHANGE, /**< Wi-Fi home channel change,doesn't occur when scanning */ WIFI_EVENT_STA_NEIGHBOR_REP, /**< Received Neighbor Report response */ @@ -1617,9 +1624,9 @@ typedef struct { } wifi_event_ndp_terminated_t; /** - * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_INDICATION event + * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_INDICATION event * - * Posted when a NAN Pairing Bootstrapping Request follow-up is received from a peer. + * Posted when a NAN Pairing Bootstrapping Request is received from a peer. * The application should respond using esp_wifi_nan_pairing_response(). */ typedef struct { @@ -1629,12 +1636,12 @@ typedef struct { uint16_t selected_method; /**< Bootstrapping method selected by initiator (wifi_nan_bootstrap_method_t) */ uint8_t is_comeback; /**< 1 if this is a comeback retry with cookie */ uint32_t cookie; /**< Comeback cookie from initiator (0 if none) */ -} wifi_event_nan_pairing_indication_t; +} wifi_event_nan_bootstrap_indication_t; /** - * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_CONFIRM event + * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED event * - * Posted when a NAN Pairing Bootstrapping Response follow-up is received, + * Posted when a NAN Pairing Bootstrapping Response is received, * or when the bootstrapping handshake completes/fails. */ typedef struct { @@ -1646,7 +1653,19 @@ typedef struct { uint8_t reason_code; /**< Rejection reason (valid if rejected) */ uint16_t comeback_after; /**< Comeback deferral time in TUs (valid if comeback) */ uint32_t cookie; /**< Comeback cookie from responder (0 if none) */ -} wifi_event_nan_pairing_confirm_t; +} wifi_event_nan_bootstrap_complete_t; + +/** + * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_CONFIRM event + * + * Posted when PASN pairwise key installation completes. + * Distinct from WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED (NPBA follow-up bootstrapping). + */ +typedef struct { + uint8_t status; /**< 0=Accepted, 1=Rejected (wifi_nan_pairing_status_t) */ + uint8_t reason_code; /**< Rejection reason (valid if rejected) */ + uint8_t peer_nmi[6]; /**< Peer's NAN Management Interface MAC */ +} wifi_event_nan_pairing_complete_t; /** * @brief Argument structure for WIFI_EVENT_STA_NEIGHBOR_REP event diff --git a/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h b/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h index 12523ae3542..3b809654f39 100644 --- a/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h +++ b/components/esp_wifi/remote/include/injected/esp_wifi_types_generic.h @@ -591,6 +591,8 @@ typedef struct { uint8_t sae_h2e_identifier[SAE_H2E_IDENTIFIER_LEN]; /**< Password identifier for H2E. Strings null-terminated (length < SAE_H2E_IDENTIFIER_LEN) or non-null terminated (length = SAE_H2E_IDENTIFIER_LEN) are accepted. Non-null terminated string with 0xFF for full length of SAE_H2E_IDENTIFIER_LEN is not considered a valid identifier */ } wifi_sta_config_t; +#define ESP_WIFI_NAN_NIK_LEN 16 /**< Length of NAN Identity Key (NIK) */ + /** * @brief NAN Discovery start configuration */ @@ -600,6 +602,9 @@ typedef struct { uint8_t scan_time; /**< Scan time in seconds while searching for a NAN cluster */ uint16_t warm_up_sec; /**< Warm up time before assuming NAN Anchor Master role */ bool disable_random_mac;/**< Disable the MAC Randomisation in NAN */ + uint8_t nik[ESP_WIFI_NAN_NIK_LEN]; /**< Optional NIK. Auto-generated when nik_valid is false. */ + uint8_t nik_valid: 1; /**< NIK present in nik[] and should be used as-is. */ + uint8_t reserved: 7; /**< Reserved for future use. */ } wifi_nan_sync_config_t; /** @@ -1287,8 +1292,10 @@ typedef enum { WIFI_EVENT_NDP_INDICATION, /**< Received NDP Request from a NAN Peer */ WIFI_EVENT_NDP_CONFIRM, /**< NDP Confirm Indication */ WIFI_EVENT_NDP_TERMINATED, /**< NAN Datapath terminated indication */ - WIFI_EVENT_NAN_PAIRING_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ - WIFI_EVENT_NAN_PAIRING_CONFIRM, /**< NAN Pairing Bootstrapping completed (success/failure) */ + WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ + WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, /**< NAN Pairing Bootstrapping completed (success/failure) */ + WIFI_EVENT_NAN_PAIRING_INDICATION, /**< Received NAN Pairing indication (reserved) */ + WIFI_EVENT_NAN_PAIRING_CONFIRM, /**< NAN PASN pairwise key installation completed */ WIFI_EVENT_HOME_CHANNEL_CHANGE, /**< Wi-Fi home channel change,doesn't occur when scanning */ WIFI_EVENT_STA_NEIGHBOR_REP, /**< Received Neighbor Report response */ @@ -1617,9 +1624,9 @@ typedef struct { } wifi_event_ndp_terminated_t; /** - * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_INDICATION event + * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_INDICATION event * - * Posted when a NAN Pairing Bootstrapping Request follow-up is received from a peer. + * Posted when a NAN Pairing Bootstrapping Request is received from a peer. * The application should respond using esp_wifi_nan_pairing_response(). */ typedef struct { @@ -1629,12 +1636,12 @@ typedef struct { uint16_t selected_method; /**< Bootstrapping method selected by initiator (wifi_nan_bootstrap_method_t) */ uint8_t is_comeback; /**< 1 if this is a comeback retry with cookie */ uint32_t cookie; /**< Comeback cookie from initiator (0 if none) */ -} wifi_event_nan_pairing_indication_t; +} wifi_event_nan_bootstrap_indication_t; /** - * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_CONFIRM event + * @brief Argument structure for WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED event * - * Posted when a NAN Pairing Bootstrapping Response follow-up is received, + * Posted when a NAN Pairing Bootstrapping Response is received, * or when the bootstrapping handshake completes/fails. */ typedef struct { @@ -1646,7 +1653,19 @@ typedef struct { uint8_t reason_code; /**< Rejection reason (valid if rejected) */ uint16_t comeback_after; /**< Comeback deferral time in TUs (valid if comeback) */ uint32_t cookie; /**< Comeback cookie from responder (0 if none) */ -} wifi_event_nan_pairing_confirm_t; +} wifi_event_nan_bootstrap_complete_t; + +/** + * @brief Argument structure for WIFI_EVENT_NAN_PAIRING_CONFIRM event + * + * Posted when PASN pairwise key installation completes. + * Distinct from WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED (NPBA follow-up bootstrapping). + */ +typedef struct { + uint8_t status; /**< 0=Accepted, 1=Rejected (wifi_nan_pairing_status_t) */ + uint8_t reason_code; /**< Rejection reason (valid if rejected) */ + uint8_t peer_nmi[6]; /**< Peer's NAN Management Interface MAC */ +} wifi_event_nan_pairing_complete_t; /** * @brief Argument structure for WIFI_EVENT_STA_NEIGHBOR_REP event diff --git a/components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h b/components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h index 9c0165712af..c3862e92e7f 100644 --- a/components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h +++ b/components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h @@ -22,6 +22,7 @@ extern "C" { .scan_time = 3, \ .warm_up_sec = 5, \ .disable_random_mac = false, \ + .nik_valid = false, \ }; #define NDP_STATUS_ACCEPTED 1 @@ -57,6 +58,25 @@ struct nan_peer_record { uint8_t peer_ndi[6]; /**< Peer's NAN Data Interface address, only valid when ndp_id is non-zero */ }; +#define NAN_PAIRING_PINCODE_MIN 000000 +#define NAN_PAIRING_PINCODE_MAX 999999 + +union pairing_cred_t { + uint32_t pincode; /**< 6-digit PIN in range of NAN_PAIRING_PINCODE_MIN to NAN_PAIRING_PINCODE_MAX */ +}; + +enum nan_pairing_role { + NAN_PAIRING_ROLE_INITIATOR, + NAN_PAIRING_ROLE_RESPONDER, +}; + +typedef struct { + uint8_t peer_svc_id; + uint8_t peer_nmi[6]; + enum nan_pairing_role self_role; + union pairing_cred_t cred; +} wifi_nan_pairing_config_t; + /** * @brief Start NAN Synchronization using the provided parameters. * @note Discovery traffic begins only after publish/subscribe services are started. @@ -202,7 +222,7 @@ typedef struct { wifi_nan_pairing_status_t status; /**< Set to COMEBACK when resending with cookie */ uint16_t comeback_after; /**< Comeback deferral time in TUs (only when status=COMEBACK) */ uint32_t cookie; /**< Opaque cookie from responder (only when status=COMEBACK) */ -} wifi_nan_pairing_bootstrapping_req_t; +} wifi_nan_pairing_bootstrap_req_t; /** * @brief NAN Pairing Bootstrapping Response parameters (responder -> initiator) @@ -235,7 +255,7 @@ typedef struct { * - ESP_ERR_INVALID_ARG: Invalid parameters * - ESP_FAIL: Failed to send */ -esp_err_t esp_wifi_nan_pairing_request(wifi_nan_pairing_bootstrapping_req_t *req); +esp_err_t esp_wifi_nan_bootstrap_request(wifi_nan_pairing_bootstrap_req_t *req); /** * @brief Respond to a NAN Pairing Bootstrapping request from a peer @@ -250,7 +270,21 @@ esp_err_t esp_wifi_nan_pairing_request(wifi_nan_pairing_bootstrapping_req_t *req * - ESP_ERR_INVALID_ARG: Invalid parameters * - ESP_FAIL: Failed to send */ -esp_err_t esp_wifi_nan_pairing_response(wifi_nan_pairing_bootstrapping_resp_t *resp); +esp_err_t esp_wifi_nan_bootstrap_response(wifi_nan_pairing_bootstrapping_resp_t *resp); + +/** + * @brief Start NAN Pairing process after credentials are shared Out-of-band + * + * @attention This API should be called after Bootstrapping is completed + * + * @param req Pairing setup parameters. + * + * @return + * - ESP_OK: Bootstrapping request follow-up sent successfully + * - ESP_ERR_INVALID_ARG: Invalid parameters + * - ESP_FAIL: Failed to send + */ +esp_err_t esp_wifi_nan_pairing_start(wifi_nan_pairing_config_t *cfg); #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c index 02db1385262..1b280353cc7 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c @@ -23,7 +23,7 @@ #include "esp_private/esp_nan_usd.h" #endif /* CONFIG_ESP_WIFI_NAN_USD_ENABLE */ #if defined(CONFIG_ESP_WIFI_NAN_SYNC_ENABLE) && defined(CONFIG_ESP_WIFI_PASN_SUPPORT) -#include "nan_pasn.h" +#include "esp_private/esp_supp_nan.h" #include "apps_private/wifi_apps_private.h" #endif @@ -31,9 +31,7 @@ #define NAN_STARTED_BIT BIT0 #define NAN_STOPPED_BIT BIT1 -/* NAN Events */ -#define NAN_TX_SUCCESS BIT2 -#define NAN_TX_FAILURE BIT3 +/* NAN Events (NAN_TX_SUCCESS / NAN_TX_FAILURE in nan_i.h) */ #define NDP_INDICATION BIT4 #define NDP_ACCEPTED BIT5 #define NDP_TERMINATED BIT6 @@ -46,8 +44,6 @@ #define MACADDR_LEN 6 #define MACADDR_EQUAL(a1, a2) (memcmp(a1, a2, MACADDR_LEN) == 0) #define MACADDR_COPY(dst, src) (memcpy(dst, src, MACADDR_LEN)) -#define NAN_DW_INTVL_MS 524 /* NAN DW interval (512 TU's ~= 524 mSec) */ -#define NAN_ACTION_TIMEOUT 4*NAN_DW_INTVL_MS /* Global Variables */ static const char *TAG = "nan_app"; @@ -74,17 +70,22 @@ static const uint8_t s_wfa_oui[3] = {0x50, 0x6f, 0x9a}; /* Definition of nan_ctx_t storage shared via nan_i.h. */ nan_ctx_t s_nan_ctx; -#if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) -struct nan_pasn_data *esp_nan_app_get_pasn_data(void) +#ifdef CONFIG_ESP_WIFI_NAN_PAIRING +EventGroupHandle_t nan_pairing_get_event_group(void) { - return s_nan_ctx.nan_pasn_data; + return nan_event_group; } -void esp_nan_app_set_pasn_data(struct nan_pasn_data *pd) +uint32_t *nan_pairing_get_fup_context(void) { - s_nan_ctx.nan_pasn_data = pd; + return &s_fup_context; } -#endif + +const uint8_t *nan_pairing_get_null_mac(void) +{ + return null_mac; +} +#endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ void esp_wifi_nan_get_ipv6_linklocal_from_mac(ip6_addr_t *ip6, uint8_t *mac_addr) { @@ -146,7 +147,7 @@ struct own_svc_info *nan_find_own_svc_by_name(const char *svc_name) return p_svc; } -static struct peer_svc_info *nan_find_peer_svc(uint8_t own_svc_id, uint8_t peer_svc_id, uint8_t peer_nmi[]) +struct peer_svc_info *nan_find_peer_svc(uint8_t own_svc_id, uint8_t peer_svc_id, uint8_t peer_nmi[]) { struct peer_svc_info *p_peer_svc = NULL, *temp; struct own_svc_info *p_own_svc = NULL; @@ -569,7 +570,7 @@ fail: return ESP_FAIL; } -static void nan_app_post_event(int32_t event_id, void* event_data, size_t event_data_size) +void nan_app_post_event(int32_t event_id, void* event_data, size_t event_data_size) { g_wifi_osi_funcs._event_post(WIFI_EVENT, event_id, event_data, event_data_size, OSI_FUNCS_TIME_BLOCKING); } @@ -721,6 +722,7 @@ void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_info) uint8_t *ssi = peer_info->ssi; uint16_t ssi_len = peer_info->ssi_len; uint32_t device_caps = peer_info->device_caps; + uint8_t *shared_key_attr = peer_info->shared_key_attr; NAN_DATA_LOCK(); if (!nan_find_peer_svc(svc_id, peer_svc_id, peer_mac)) { @@ -728,6 +730,13 @@ void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_info) } NAN_DATA_UNLOCK(); +#if defined(CONFIG_ESP_WIFI_NAN_SECURITY) && defined(CONFIG_ESP_WIFI_NAN_PAIRING) && defined(CONFIG_ESP_WIFI_PASN_SUPPORT) + if (shared_key_attr) { + nan_app_receive_pairing_followup(svc_id, peer_svc_id, peer_mac, shared_key_attr); + return; + } +#endif + size_t evt_data_len = sizeof(wifi_event_nan_receive_t) + ssi_len; wifi_event_nan_receive_t *evt = (wifi_event_nan_receive_t *)os_zalloc(evt_data_len); if (!evt) { @@ -741,8 +750,7 @@ void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_info) if (ssi && ssi_len) { memcpy(evt->ssi, ssi, ssi_len); evt->ssi_len = ssi_len; - ESP_LOGE(TAG, "Received payload from Peer "MACSTR" [Peer Service id - %d] - ", MAC2STR(peer_mac), peer_svc_id); - ESP_LOG_BUFFER_HEXDUMP(TAG, ssi, ssi_len, ESP_LOG_INFO); + ESP_LOGD(TAG, "Received payload from Peer "MACSTR" [Peer Service id - %d] - ", MAC2STR(peer_mac), peer_svc_id); } nan_app_post_event(WIFI_EVENT_NAN_RECEIVE, evt, evt_data_len); @@ -1002,6 +1010,10 @@ void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer_info, goto done; } +#ifndef NAN_KEY_ND_TK +#define NAN_KEY_ND_TK 0 +#endif + #ifdef CONFIG_ESP_WIFI_NAN_SECURITY if (ndl->security_ctx.type == WIFI_NAN_SECURITY_ENCRYPTED) { uint8_t key_rsc[8] = {0}; @@ -1013,7 +1025,7 @@ void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer_info, sizeof(key_rsc), ndl->nd_tk, NAN_NCS_SK_128_TK_LEN, - NAN_KEY_FLAG_PAIRWISE | NAN_KEY_FLAG_RX | NAN_KEY_FLAG_TX); + NAN_KEY_ND_TK); if (ret != 0) { ESP_LOGE(TAG, "NDP confirm: failed to install NAN pairwise key (ndp_id=%d, ret=%d)", ndp_id, ret); os_free(evt); @@ -1184,41 +1196,6 @@ static struct nan_secure_dp_funcs s_nan_secure_dp_funcs = { #endif /* CONFIG_ESP_WIFI_NAN_SECURITY */ }; -#ifdef CONFIG_ESP_WIFI_NAN_PAIRING -void nan_app_pairing_indication_cb(uint8_t peer_svc_id, uint8_t pub_id, - uint8_t peer_nmi[6], uint16_t selected_method) -{ - ESP_LOGI(TAG, "Pairing Bootstraping Request from "MACSTR" [pub_id=%d, method=0x%x]", - MAC2STR(peer_nmi), pub_id, selected_method); - - wifi_event_nan_pairing_indication_t evt = {0}; - evt.peer_svc_id = peer_svc_id; - evt.own_svc_id = pub_id; - MACADDR_COPY(evt.peer_nmi, peer_nmi); - evt.selected_method = selected_method; - - nan_app_post_event(WIFI_EVENT_NAN_PAIRING_INDICATION, &evt, sizeof(evt)); -} - -void nan_app_pairing_confirm_cb(uint8_t status, uint8_t peer_svc_id, uint8_t sub_id, - uint8_t peer_nmi[6], uint16_t matched_method, - uint8_t reason_code) -{ - ESP_LOGI(TAG, "Pairing Bootstraping Response from "MACSTR" [sub_id=%d, status=%d, method=0x%x]", - MAC2STR(peer_nmi), sub_id, status, matched_method); - - wifi_event_nan_pairing_confirm_t evt = {0}; - evt.status = status; - evt.peer_svc_id = peer_svc_id; - evt.own_svc_id = sub_id; - MACADDR_COPY(evt.peer_nmi, peer_nmi); - evt.matched_method = matched_method; - evt.reason_code = reason_code; - - nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); -} -#endif /* CONFIG_ESP_WIFI_NAN_PAIRIN */ - void esp_nan_app_deinit(void) { esp_nan_internal_register_secure_dp_funcs(NULL); @@ -1283,13 +1260,11 @@ void esp_nan_action_start(esp_netif_t *nan_netif) .action_txdone = nan_action_txdone_cb, .ndp_response_indication = nan_app_ndp_response_indication_cb, #ifdef CONFIG_ESP_WIFI_NAN_PAIRING - .pairing_indication = nan_app_pairing_indication_cb, - .pairing_confirm = nan_app_pairing_confirm_cb, -#endif -#if defined(CONFIG_ESP_WIFI_NAN_SYNC_ENABLE) && defined(CONFIG_ESP_WIFI_PASN_SUPPORT) + .pairing_indication = nan_app_bootstrap_indication_cb, + .pairing_confirm = nan_app_bootstrap_completed_cb, + .get_nira_len = esp_nan_get_nira_len, + .construct_nira = esp_nan_construct_nira, .receive_pasn = handle_auth_pasn, -#else - .receive_pasn = NULL, #endif }; esp_nan_internal_register_callbacks(&nan_cb); @@ -1322,6 +1297,11 @@ esp_err_t esp_wifi_nan_sync_start(const wifi_nan_sync_config_t *nan_cfg) wifi_mode_t mode; esp_err_t ret; + if (!nan_cfg) { + ESP_LOGE(TAG, "NAN start config is NULL"); + return ESP_ERR_INVALID_ARG; + } + ret = esp_wifi_get_mode(&mode); if (ret == ESP_ERR_WIFI_NOT_INIT) { ESP_LOGE(TAG, "WiFi not initialised!"); @@ -1346,6 +1326,19 @@ esp_err_t esp_wifi_nan_sync_start(const wifi_nan_sync_config_t *nan_cfg) NAN_DATA_UNLOCK(); return ESP_OK; } +#ifdef CONFIG_ESP_WIFI_NAN_SECURITY + if (nan_cfg->nik_valid) { + memcpy(s_nan_ctx.own_nik, nan_cfg->nik, ESP_WIFI_NAN_NIK_LEN); + s_nan_ctx.own_nik_valid = true; + } else { + if (os_get_random(s_nan_ctx.own_nik, ESP_WIFI_NAN_NIK_LEN) != 0) { + NAN_DATA_UNLOCK(); + ESP_LOGE(TAG, "Failed to generate NAN NIK"); + return ESP_FAIL; + } + s_nan_ctx.own_nik_valid = true; + } +#endif NAN_DATA_UNLOCK(); ESP_RETURN_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_NAN), TAG, "Set mode NAN failed"); @@ -1454,13 +1447,9 @@ uint8_t esp_wifi_nan_publish_service(const wifi_nan_publish_cfg_t *publish_cfg) } #ifdef CONFIG_ESP_WIFI_NAN_PAIRING - /* Validate pairing bootstrapping methods for Publisher */ - if (publish_cfg->pairing.bootstrapping_methods) { - uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_DISPLAY; - if (publish_cfg->pairing.bootstrapping_methods & ~valid_methods) { - ESP_LOGE(TAG, "Invalid bootstrapping methods for Publisher. Only OPPORTUNISTIC and PIN_CODE_DISPLAY allowed"); - return 0; - } + if (!nan_pairing_validate_publish_bootstrapping(publish_cfg->pairing.bootstrapping_methods)) { + ESP_LOGE(TAG, "Invalid bootstrapping methods for Publisher. Only OPPORTUNISTIC and PIN_CODE_DISPLAY allowed"); + return 0; } #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ @@ -1515,8 +1504,12 @@ uint8_t esp_wifi_nan_publish_service(const wifi_nan_publish_cfg_t *publish_cfg) } for (uint8_t i = 0; i < publish_cfg->security_cfg->num_credentials; i++) { uint8_t csid = publish_cfg->security_cfg->creds[i].csid; - if (csid != WIFI_NAN_CSID_NCS_SK_128) { - ESP_LOGE(TAG, "creds[%u].csid=%u unsupported (only NCS-SK-128)", i, csid); + if (csid != WIFI_NAN_CSID_NCS_SK_128 +#if CONFIG_ESP_WIFI_NAN_PAIRING + && csid != WIFI_NAN_CSID_NCS_PK_PASN_128 +#endif + ) { + ESP_LOGE(TAG, "creds[%u].csid=%u unsupported cipher", i, csid); goto fail; } } @@ -1600,13 +1593,9 @@ uint8_t esp_wifi_nan_subscribe_service(const wifi_nan_subscribe_cfg_t *subscribe } #ifdef CONFIG_ESP_WIFI_NAN_PAIRING - /* Validate pairing bootstrapping methods for Subscriber */ - if (subscribe_cfg->pairing.bootstrapping_methods) { - uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_KEYPAD; - if (subscribe_cfg->pairing.bootstrapping_methods & ~valid_methods) { - ESP_LOGE(TAG, "Invalid bootstrapping methods for Subscriber. Only OPPORTUNISTIC and PIN_CODE_KEYPAD allowed"); - return 0; - } + if (!nan_pairing_validate_subscribe_bootstrapping(subscribe_cfg->pairing.bootstrapping_methods)) { + ESP_LOGE(TAG, "Invalid bootstrapping methods for Subscriber. Only OPPORTUNISTIC and PIN_CODE_KEYPAD allowed"); + return 0; } #endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ @@ -1642,8 +1631,12 @@ uint8_t esp_wifi_nan_subscribe_service(const wifi_nan_subscribe_cfg_t *subscribe } for (uint8_t i = 0; i < subscribe_cfg->security_cfg->num_credentials; i++) { uint8_t csid = subscribe_cfg->security_cfg->creds[i].csid; - if (csid != WIFI_NAN_CSID_NCS_SK_128) { - ESP_LOGE(TAG, "creds[%u].csid=%u unsupported (only NCS-SK-128)", i, csid); + if (csid != WIFI_NAN_CSID_NCS_SK_128 +#if CONFIG_ESP_WIFI_NAN_PAIRING + && csid != WIFI_NAN_CSID_NCS_PK_PASN_128 +#endif + ) { + ESP_LOGE(TAG, "creds[%u].csid=%u unsupported cipher", i, csid); return 0; } } @@ -1776,120 +1769,6 @@ esp_err_t esp_wifi_nan_send_message(wifi_nan_followup_params_t *fup_params) return ret; } -#ifdef CONFIG_ESP_WIFI_NAN_PAIRING -static esp_err_t nan_send_pairing_followup(uint8_t inst_id, uint8_t peer_inst_id, - uint8_t *peer_mac, - wifi_nan_pairing_npba_params_t *pairing_npba) -{ - struct peer_svc_info *p_peer_svc; - NAN_DATA_LOCK(); - p_peer_svc = nan_find_peer_svc(inst_id, peer_inst_id, peer_mac); - if (!p_peer_svc) { - ESP_LOGE(TAG, "Cannot send Pairing follow-up, peer not found!"); - NAN_DATA_UNLOCK(); - return ESP_FAIL; - } - - wifi_nan_followup_params_t fup_params = {0}; - fup_params.inst_id = inst_id ? inst_id : p_peer_svc->own_svc_id; - fup_params.peer_inst_id = peer_inst_id ? peer_inst_id : p_peer_svc->svc_id; - if (!MACADDR_EQUAL(peer_mac, null_mac)) { - MACADDR_COPY(fup_params.peer_mac, p_peer_svc->peer_nmi); - } else { - MACADDR_COPY(fup_params.peer_mac, peer_mac); - } - NAN_DATA_UNLOCK(); - - os_event_group_clear_bits(nan_event_group, NAN_TX_SUCCESS | NAN_TX_FAILURE); - esp_err_t ret = esp_nan_internal_send_followup(&fup_params, &s_fup_context, pairing_npba); - if (ret != ESP_OK) { - return ret; - } - - EventBits_t bits = os_event_group_wait_bits(nan_event_group, - NAN_TX_SUCCESS | NAN_TX_FAILURE, pdFALSE, pdFALSE, - pdMS_TO_TICKS(NAN_ACTION_TIMEOUT)); - if (bits & NAN_TX_SUCCESS) { - return ESP_OK; - } - return ESP_FAIL; -} - -esp_err_t esp_wifi_nan_pairing_request(wifi_nan_pairing_bootstrapping_req_t *req) -{ - if (!req) { - ESP_LOGE(TAG, "Pairing request params NULL"); - return ESP_ERR_INVALID_ARG; - } - - if (!req->selected_method || - (req->selected_method & (req->selected_method - 1))) { - ESP_LOGE(TAG, "Exactly one bootstrapping method must be selected"); - return ESP_ERR_INVALID_ARG; - } - - uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_KEYPAD; - if (req->selected_method & ~valid_methods) { - ESP_LOGE(TAG, "Invalid bootstrapping method for initiator (subscriber)"); - return ESP_ERR_INVALID_ARG; - } - - wifi_nan_pairing_npba_params_t pairing_npba = { - .type = WIFI_NAN_NPBA_TYPE_REQUEST, - .status = req->status, - .method = req->selected_method, - .reason_code = 0, - .comeback_after = req->comeback_after, - .cookie = req->cookie, - }; - - esp_err_t ret = nan_send_pairing_followup(req->inst_id, req->peer_inst_id, - req->peer_mac, &pairing_npba); - if (ret == ESP_OK) { - ESP_LOGI(TAG, "Sent Pairing Bootstrapping request to Peer "MACSTR" [method=0x%x]", - MAC2STR(req->peer_mac), req->selected_method); - } else { - ESP_LOGE(TAG, "Failed to send Pairing Bootstrapping request!"); - } - return ret; -} - -esp_err_t esp_wifi_nan_pairing_response(wifi_nan_pairing_bootstrapping_resp_t *resp) -{ - if (!resp) { - ESP_LOGE(TAG, "Pairing response params NULL"); - return ESP_ERR_INVALID_ARG; - } - - if (resp->status == WIFI_NAN_PAIRING_STATUS_ACCEPTED) { - uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_DISPLAY; - if (!resp->matched_method || (resp->matched_method & ~valid_methods)) { - ESP_LOGE(TAG, "Invalid matched bootstrapping method for responder (publisher)"); - return ESP_ERR_INVALID_ARG; - } - } - - wifi_nan_pairing_npba_params_t pairing_npba = { - .type = WIFI_NAN_NPBA_TYPE_RESPONSE, - .status = resp->status, - .method = resp->matched_method, - .reason_code = resp->reason_code, - .comeback_after = resp->comeback_after, - .cookie = resp->cookie, - }; - - esp_err_t ret = nan_send_pairing_followup(resp->inst_id, resp->peer_inst_id, - resp->peer_mac, &pairing_npba); - if (ret == ESP_OK) { - ESP_LOGI(TAG, "Sent Pairing Bootstrapping response to Peer "MACSTR" [status=%d]", - MAC2STR(resp->peer_mac), resp->status); - } else { - ESP_LOGE(TAG, "Failed to send Pairing Bootstrapping response!"); - } - return ret; -} -#endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ - esp_err_t esp_wifi_nan_cancel_service(uint8_t service_id) { #ifdef CONFIG_ESP_WIFI_NAN_USD_ENABLE @@ -2347,99 +2226,3 @@ wifi_nan_usd_config_t esp_wifi_usd_get_default_subscribe_cfg(void) return cfg; } #endif /* CONFIG_ESP_WIFI_NAN_USD_ENABLE */ - -#define NAN_ATTR_ID_IDENTITY_RESOLUTION 0x2B -/* NIRA: ID(1) + Len(2) + CipherVersion(1) + Nonce(8) + Tag(8) = 20 */ -#define NAN_NIRA_ATTR_LEN 20 - -uint32_t esp_nan_get_nira_len(void) -{ - return NAN_NIRA_ATTR_LEN; -} - -/** - * @brief Construct dummy NAN Identity Resolution Attribute (NIRA) - * - * Format: ID(1) + Length(2) + Cipher Version(1) + Nonce(8) + Tag(8) - * Cipher Version 0: 128-bit NIK, 64-bit Nonce, 64-bit Tag, HMAC-SHA-256 - * Uses random nonce and tag for now (real implementation derives tag from NIK). - */ -int esp_nan_construct_nira(uint8_t *frm) -{ - if (!frm) { - return 0; - } - - uint8_t *p = frm; - - /* Attribute ID (0x2B) */ - *p++ = NAN_ATTR_ID_IDENTITY_RESOLUTION; - - /* Attribute Length: CipherVersion(1) + Nonce(8) + Tag(8) = 17 */ - uint16_t attr_len = 17; - *p++ = attr_len & 0xFF; - *p++ = (attr_len >> 8) & 0xFF; - - /* Cipher Version: 0 (128-bit NIK, 64-bit Nonce, 64-bit Tag, HMAC-SHA-256) */ - *p++ = 0; - - /* Nonce: 8 bytes random */ - os_get_random(p, 8); - p += 8; - - /* Tag: 8 bytes random (dummy - real impl: Truncate-64(HMAC-SHA-256(NIK, "NIR", NMI || Nonce))) */ - os_get_random(p, 8); - p += 8; - - ESP_LOGI(TAG, "Constructed NIRA (dummy): len=%d", (int)(p - frm)); - return (int)(p - frm); -} - -#define NAN_MME_ELEMENT_ID 0x4C -/* MME: ElementID(1) + Length(1) + KeyID(2) + IPN(6) + MIC(8) = 18 */ -#define NAN_MME_LEN 18 - -uint32_t esp_nan_get_mme_len(void) -{ - return NAN_MME_LEN; -} - -/** - * @brief Construct dummy Management MIC Element (MME) - * - * Format: Element ID(1) + Length(1) + Key ID(2) + IPN/BIPN(6) + MIC(8) - * Key ID is set to 4, MIC is 8 bytes random (dummy). - */ -int esp_nan_construct_mme(uint8_t *frm, uint32_t ipn) -{ - if (!frm) { - return 0; - } - - uint8_t *p = frm; - - /* Element ID */ - *p++ = NAN_MME_ELEMENT_ID; - - /* Length: KeyID(2) + IPN(6) + MIC(8) = 16 */ - *p++ = 16; - - /* Key ID: 4 (LE16) */ - *p++ = 4; - *p++ = 0; - - /* IPN/BIPN: 6 bytes from ipn parameter (LE, zero-padded upper 2 bytes) */ - *p++ = (uint8_t)(ipn & 0xFF); - *p++ = (uint8_t)((ipn >> 8) & 0xFF); - *p++ = (uint8_t)((ipn >> 16) & 0xFF); - *p++ = (uint8_t)((ipn >> 24) & 0xFF); - *p++ = 0; - *p++ = 0; - - /* MIC: 8 bytes random (dummy) */ - os_get_random(p, 8); - p += 8; - - ESP_LOGI(TAG, "Constructed MME (dummy): len=%d, ipn=0x%lx", (int)(p - frm), (unsigned long)ipn); - return (int)(p - frm); -} diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h b/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h index 0f2c595bda0..622d9234796 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_i.h @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 * - * Internal declarations shared between nan_app.c and nan_security.c. + * Internal declarations shared between nan_app.c, nan_security.c, and nan_pairing.c. */ #pragma once @@ -13,6 +13,7 @@ #include #include #include "esp_err.h" +#include "esp_bit_defs.h" #include "esp_wifi_types_generic.h" #include "esp_private/wifi.h" #include "esp_nan.h" @@ -29,6 +30,18 @@ extern "C" { #define MACADDR_EQUAL(a1, a2) (memcmp(a1, a2, MACADDR_LEN) == 0) #define MACADDR_COPY(dst, src) (memcpy(dst, src, MACADDR_LEN)) +/* NAN sync event-group bits and follow-up TX timeout (nan_app.c, nan_pairing.c). */ +#ifndef NAN_DW_INTVL_MS +#define NAN_DW_INTVL_MS 524 +#endif +#ifndef NAN_TX_SUCCESS +#define NAN_TX_SUCCESS BIT(2) +#define NAN_TX_FAILURE BIT(3) +#endif +#ifndef NAN_ACTION_TIMEOUT +#define NAN_ACTION_TIMEOUT (4 * NAN_DW_INTVL_MS) +#endif + /* * Shared lock used by both files. * @@ -143,6 +156,9 @@ enum nan_handshake_state { }; #endif /* CONFIG_ESP_WIFI_NAN_SECURITY */ +/* NIK length for cipher version 0 (Wi-Fi Aware spec v4.0). */ +#define NAN_APP_PEER_NIK_LEN 16 + /* Per-peer service info */ struct peer_svc_info { SLIST_ENTRY(peer_svc_info) next; @@ -159,6 +175,16 @@ struct peer_svc_info { * uses this to pick the right credential for M1's pair-PMKID. */ uint8_t matched_cred_idx; #endif +#if CONFIG_ESP_WIFI_NAN_PAIRING + /* Peer NIK / cipher version / lifetime extracted from a NAN Shared Key + * Descriptor attribute received in a follow-up frame. @c has_nik is set + * once a valid NIK has been decrypted and stored. + */ + uint8_t peer_nik[NAN_APP_PEER_NIK_LEN]; + uint8_t peer_nik_cipher_ver; + uint32_t peer_nik_lifetime_sec; + bool has_nik; +#endif }; /* Own (locally registered) service info */ @@ -241,6 +267,11 @@ typedef struct { struct ndl_info ndl[ESP_WIFI_NAN_DATAPATH_MAX_PEERS]; struct own_svc_info own_svc[ESP_WIFI_NAN_MAX_SVC_SUPPORTED]; esp_netif_t *nan_netif; +#ifdef CONFIG_ESP_WIFI_NAN_SECURITY + /* Own NAN Identity Key (NIK) cached for pairing/security flows. */ + uint8_t own_nik[ESP_WIFI_NAN_NIK_LEN]; + bool own_nik_valid; +#endif #ifdef CONFIG_ESP_WIFI_PASN_SUPPORT struct nan_pasn_data *nan_pasn_data; #endif @@ -260,6 +291,26 @@ struct ndl_info *nan_find_ndl_by_pub_id_and_peer(uint8_t pub_id, const uint8_t * void esp_nan_ndp_tx_done_cb(uint8_t ndp_id, const uint8_t *peer_nmi, uint8_t msg_type, bool tx_status); +#ifdef CONFIG_ESP_WIFI_NAN_PAIRING +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +void nan_app_post_event(int32_t event_id, void *event_data, size_t event_data_size); +struct peer_svc_info *nan_find_peer_svc(uint8_t own_svc_id, uint8_t peer_svc_id, uint8_t peer_nmi[]); +EventGroupHandle_t nan_pairing_get_event_group(void); +uint32_t *nan_pairing_get_fup_context(void); +const uint8_t *nan_pairing_get_null_mac(void); + +bool nan_pairing_validate_publish_bootstrapping(uint16_t bootstrapping_methods); +bool nan_pairing_validate_subscribe_bootstrapping(uint16_t bootstrapping_methods); + +void nan_app_bootstrap_indication_cb(uint8_t peer_svc_id, uint8_t pub_id, + uint8_t peer_nmi[6], uint16_t selected_method); +void nan_app_bootstrap_completed_cb(uint8_t status, uint8_t peer_svc_id, uint8_t sub_id, + uint8_t peer_nmi[6], uint16_t matched_method, + uint8_t reason_code); +#endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ + #ifdef CONFIG_ESP_WIFI_NAN_SECURITY /* Security-gated (defined in nan_security.c) */ uint32_t esp_nan_get_csia_len(uint16_t own_csid_bitmap, uint16_t peer_csid_bitmap); @@ -352,26 +403,21 @@ esp_err_t nan_security_populate_initiator_ndl(struct ndl_info *ndl, const uint8_t *peer_nmi); /* - * Compare locally derived ND-PMKID (subscriber passphrase, publisher NMI) to - * peer discovery security params. own_svc identifies the local subscribe - * (service_name + creds[]) and peer_svc is updated with matched_cred_idx on - * success. Returns true if any local cred's PMKID matches a peer-advertised one. + * Match subscriber discovery security to a publisher's params. + * NCS-SK: Compare locally derived ND-PMKID (subscriber passphrase, publisher NMI) to + * peer discovery security params. Returns true if any local cred's PMKID matches a peer-advertised one. + * NCS-PASN: Returns true for matching cipher in Publisher's csid_bitmap */ bool nan_security_service_match(const struct own_svc_info *own_svc, struct peer_svc_info *peer_svc, const uint8_t *publisher_nmi, const wifi_nan_peer_sdf_security_t *peer_sec); -#else -static inline esp_err_t nan_derive_security_params(const char *service_name, - const wifi_nan_discovery_security_params_t *sec_cfg, - wifi_nan_security_params_t *out_derived) -{ - (void)service_name; - (void)sec_cfg; - (void)out_derived; - return ESP_FAIL; -} #endif /* CONFIG_ESP_WIFI_NAN_SECURITY */ +#if CONFIG_ESP_WIFI_NAN_PAIRING +void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, + const uint8_t *peer_mac, + const uint8_t *shared_key_attr); +#endif #ifdef __cplusplus } diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c new file mode 100644 index 00000000000..1fdf665521e --- /dev/null +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_pairing.c @@ -0,0 +1,835 @@ +/* + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * NAN pairing bootstrapping, PASN pairing follow-up, and related frame helpers. + */ + +#include "sdkconfig.h" + +#if defined(CONFIG_ESP_WIFI_NAN_PAIRING) + +#include +#include "esp_wifi.h" +#include "esp_private/wifi.h" +#include "esp_private/wifi_types.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "esp_nan.h" +#include "nan_i.h" +#include "os.h" +#include "utils/common.h" + +#if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) +#include "esp_private/esp_supp_nan.h" +#include "apps_private/wifi_apps_private.h" +#endif + +static const char *TAG = "nan_pairing"; + +#if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) +struct nan_pasn_data *esp_nan_app_get_pasn_data(void) +{ + return s_nan_ctx.nan_pasn_data; +} + +void esp_nan_app_set_pasn_data(struct nan_pasn_data *pd) +{ + s_nan_ctx.nan_pasn_data = pd; +} + +static void nan_pairing_key_installed_cb(const uint8_t *peer_nmi) +{ + wifi_event_nan_pairing_complete_t evt = {0}; + + if (!peer_nmi) { + return; + } + + evt.status = WIFI_NAN_PAIRING_STATUS_ACCEPTED; + evt.reason_code = 0; + MACADDR_COPY(evt.peer_nmi, peer_nmi); + nan_app_post_event(WIFI_EVENT_NAN_PAIRING_CONFIRM, &evt, sizeof(evt)); +} +#endif + +bool nan_pairing_validate_publish_bootstrapping(uint16_t bootstrapping_methods) +{ + if (!bootstrapping_methods) { + return true; + } + uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_DISPLAY; + return (bootstrapping_methods & ~valid_methods) == 0; +} + +bool nan_pairing_validate_subscribe_bootstrapping(uint16_t bootstrapping_methods) +{ + if (!bootstrapping_methods) { + return true; + } + uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_KEYPAD; + return (bootstrapping_methods & ~valid_methods) == 0; +} + +void nan_app_bootstrap_indication_cb(uint8_t peer_svc_id, uint8_t pub_id, + uint8_t peer_nmi[6], uint16_t selected_method) +{ + ESP_LOGI(TAG, "Pairing Bootstrapping Request from "MACSTR" [pub_id=%d, method=0x%x]", + MAC2STR(peer_nmi), pub_id, selected_method); + + wifi_event_nan_bootstrap_indication_t evt = {0}; + evt.peer_svc_id = peer_svc_id; + evt.own_svc_id = pub_id; + MACADDR_COPY(evt.peer_nmi, peer_nmi); + evt.selected_method = selected_method; + + nan_app_post_event(WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, &evt, sizeof(evt)); +} + +void nan_app_bootstrap_completed_cb(uint8_t status, uint8_t peer_svc_id, uint8_t sub_id, + uint8_t peer_nmi[6], uint16_t matched_method, + uint8_t reason_code) +{ + ESP_LOGI(TAG, "Pairing Bootstrapping Response from "MACSTR" [sub_id=%d, status=%d, method=0x%x]", + MAC2STR(peer_nmi), sub_id, status, matched_method); + + wifi_event_nan_bootstrap_complete_t evt = {0}; + evt.status = status; + evt.peer_svc_id = peer_svc_id; + evt.own_svc_id = sub_id; + MACADDR_COPY(evt.peer_nmi, peer_nmi); + evt.matched_method = matched_method; + evt.reason_code = reason_code; + + nan_app_post_event(WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, &evt, sizeof(evt)); +} + +static esp_err_t nan_send_bootstrap_followup(uint8_t inst_id, uint8_t peer_inst_id, + uint8_t *peer_mac, + wifi_nan_pairing_npba_params_t *pairing_npba) +{ + struct peer_svc_info *p_peer_svc; + NAN_DATA_LOCK(); + p_peer_svc = nan_find_peer_svc(inst_id, peer_inst_id, peer_mac); + if (!p_peer_svc) { + ESP_LOGE(TAG, "Cannot send Pairing follow-up, peer not found!"); + NAN_DATA_UNLOCK(); + return ESP_FAIL; + } + + wifi_nan_followup_params_t fup_params = {0}; + fup_params.inst_id = inst_id ? inst_id : p_peer_svc->own_svc_id; + fup_params.peer_inst_id = peer_inst_id ? peer_inst_id : p_peer_svc->svc_id; + if (!MACADDR_EQUAL(peer_mac, nan_pairing_get_null_mac())) { + MACADDR_COPY(fup_params.peer_mac, p_peer_svc->peer_nmi); + } else { + MACADDR_COPY(fup_params.peer_mac, peer_mac); + } + NAN_DATA_UNLOCK(); + + extra_params_internal_t params_i = { .pairing_npba = pairing_npba }; + + os_event_group_clear_bits(nan_pairing_get_event_group(), NAN_TX_SUCCESS | NAN_TX_FAILURE); + esp_err_t ret = esp_nan_internal_send_followup(&fup_params, nan_pairing_get_fup_context(), ¶ms_i); + if (ret != ESP_OK) { + return ret; + } + + EventBits_t bits = os_event_group_wait_bits(nan_pairing_get_event_group(), + NAN_TX_SUCCESS | NAN_TX_FAILURE, pdFALSE, pdFALSE, + pdMS_TO_TICKS(NAN_ACTION_TIMEOUT)); + if (bits & NAN_TX_SUCCESS) { + return ESP_OK; + } + return ESP_FAIL; +} + +esp_err_t esp_wifi_nan_bootstrap_request(wifi_nan_pairing_bootstrap_req_t *req) +{ + if (!req) { + ESP_LOGE(TAG, "Pairing request params NULL"); + return ESP_ERR_INVALID_ARG; + } + + if (!req->selected_method || + (req->selected_method & (req->selected_method - 1))) { + ESP_LOGE(TAG, "Exactly one bootstrapping method must be selected"); + return ESP_ERR_INVALID_ARG; + } + + uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_KEYPAD; + if (req->selected_method & ~valid_methods) { + ESP_LOGE(TAG, "Invalid bootstrapping method for initiator (subscriber)"); + return ESP_ERR_INVALID_ARG; + } + + wifi_nan_pairing_npba_params_t pairing_npba = { + .type = WIFI_NAN_NPBA_TYPE_REQUEST, + .status = req->status, + .method = req->selected_method, + .reason_code = 0, + .comeback_after = req->comeback_after, + .cookie = req->cookie, + }; + + esp_err_t ret = nan_send_bootstrap_followup(req->inst_id, req->peer_inst_id, + req->peer_mac, &pairing_npba); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Sent Pairing Bootstrapping request to Peer "MACSTR" [method=0x%x]", + MAC2STR(req->peer_mac), req->selected_method); + } else { + ESP_LOGE(TAG, "Failed to send Pairing Bootstrapping request!"); + } + return ret; +} + +esp_err_t esp_wifi_nan_bootstrap_response(wifi_nan_pairing_bootstrapping_resp_t *resp) +{ + if (!resp) { + ESP_LOGE(TAG, "Pairing response params NULL"); + return ESP_ERR_INVALID_ARG; + } + + if (resp->status == WIFI_NAN_PAIRING_STATUS_ACCEPTED) { + uint16_t valid_methods = WIFI_NAN_BOOTSTRAP_OPPORTUNISTIC | WIFI_NAN_BOOTSTRAP_PIN_CODE_DISPLAY; + if (!resp->matched_method || (resp->matched_method & ~valid_methods)) { + ESP_LOGE(TAG, "Invalid matched bootstrapping method for responder (publisher)"); + return ESP_ERR_INVALID_ARG; + } + } + + wifi_nan_pairing_npba_params_t pairing_npba = { + .type = WIFI_NAN_NPBA_TYPE_RESPONSE, + .status = resp->status, + .method = resp->matched_method, + .reason_code = resp->reason_code, + .comeback_after = resp->comeback_after, + .cookie = resp->cookie, + }; + + esp_err_t ret = nan_send_bootstrap_followup(resp->inst_id, resp->peer_inst_id, + resp->peer_mac, &pairing_npba); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Sent Pairing Bootstrapping response to Peer "MACSTR" [status=%d]", + MAC2STR(resp->peer_mac), resp->status); + } else { + ESP_LOGE(TAG, "Failed to send Pairing Bootstrapping response!"); + } + return ret; +} + +esp_err_t esp_wifi_nan_pairing_start(wifi_nan_pairing_config_t *cfg) +{ + if (!cfg) { + ESP_LOGE(TAG, "Pairing config NULL"); + return ESP_ERR_INVALID_ARG; + } + +#if defined(CONFIG_ESP_WIFI_PASN_SUPPORT) + int ret; + + switch (cfg->self_role) { + case NAN_PAIRING_ROLE_RESPONDER: + ret = esp_nan_supp_pasn_responder_init(cfg->peer_nmi, cfg->cred.pincode, + nan_pairing_key_installed_cb); + if (ret != 0) { + ESP_LOGE(TAG, "NAN PASN responder init failed for "MACSTR, MAC2STR(cfg->peer_nmi)); + return ESP_FAIL; + } + break; + case NAN_PAIRING_ROLE_INITIATOR: + ret = esp_nan_supp_pasn_initiator_auth(cfg->peer_nmi, cfg->cred.pincode, + nan_pairing_key_installed_cb); + if (ret != 0) { + ESP_LOGE(TAG, "NAN PASN initiator auth failed for "MACSTR, MAC2STR(cfg->peer_nmi)); + return ESP_FAIL; + } + break; + default: + ESP_LOGE(TAG, "Invalid pairing role %d", cfg->self_role); + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +#else + ESP_LOGE(TAG, "NAN PASN support not enabled"); + return ESP_ERR_NOT_SUPPORTED; +#endif +} + +/* NIRA: ID(1) + Len(2) + CipherVersion(1) + Nonce(8) + Tag(8) = 20 */ +#define NAN_ATTR_ID_IDENTITY_RESOLUTION 0x2B +#define NAN_NIRA_NONCE_LEN 8 +#define NAN_NIRA_TAG_LEN 8 +#define NAN_NIRA_CIPHER_VER 0 +#define NAN_NIRA_ATTR_LEN (3 + 1 + NAN_NIRA_NONCE_LEN + NAN_NIRA_TAG_LEN) +#define NAN_NIRA_STR "NIR" +#define NAN_NIRA_STR_LEN 3 + +uint32_t esp_nan_get_nira_len(void) +{ + return NAN_NIRA_ATTR_LEN; +} + +int esp_nan_construct_nira(uint8_t *frm) +{ + uint8_t nonce[NAN_NIRA_NONCE_LEN]; + uint8_t tag[NAN_NIRA_TAG_LEN]; + + if (!frm) { + return 0; + } + + if (os_get_random(nonce, sizeof(nonce)) != 0) { + ESP_LOGE(TAG, "NIRA: failed to generate nonce"); + return 0; + } + +#ifdef CONFIG_ESP_WIFI_NAN_SECURITY + uint8_t own_nmi[MACADDR_LEN]; + const unsigned char *addr[3]; + int len_arr[3]; + uint8_t digest[32]; + + if (!s_nan_ctx.own_nik_valid) { + ESP_LOGW(TAG, "NIRA: own NIK is not available"); + return 0; + } + if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) { + ESP_LOGE(TAG, "NIRA: hmac_sha256_vector not registered"); + return 0; + } + if (esp_wifi_get_mac(WIFI_IF_NAN, own_nmi) != ESP_OK) { + ESP_LOGE(TAG, "NIRA: failed to read NAN NMI"); + return 0; + } + + /* Tag = Truncate-64(HMAC-SHA-256(NIK, "NIR" || NMI || Nonce)) */ + addr[0] = (const unsigned char *)NAN_NIRA_STR; + len_arr[0] = NAN_NIRA_STR_LEN; + addr[1] = own_nmi; + len_arr[1] = MACADDR_LEN; + addr[2] = nonce; + len_arr[2] = NAN_NIRA_NONCE_LEN; + if (g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(s_nan_ctx.own_nik, + ESP_WIFI_NAN_NIK_LEN, + 3, addr, len_arr, + digest) != 0) { + ESP_LOGE(TAG, "NIRA: tag derivation failed"); + return 0; + } + memcpy(tag, digest, NAN_NIRA_TAG_LEN); + memset(digest, 0, sizeof(digest)); +#else + /* NIRA requires an available NIK; skip when NAN security is not enabled. */ + return 0; +#endif + + uint8_t *p = frm; + *p++ = NAN_ATTR_ID_IDENTITY_RESOLUTION; + /* Attribute Length: CipherVersion(1) + Nonce(8) + Tag(8) = 17 */ + *p++ = 17 & 0xFF; + *p++ = (17 >> 8) & 0xFF; + *p++ = NAN_NIRA_CIPHER_VER; + memcpy(p, nonce, NAN_NIRA_NONCE_LEN); + p += NAN_NIRA_NONCE_LEN; + memcpy(p, tag, NAN_NIRA_TAG_LEN); + p += NAN_NIRA_TAG_LEN; + + return (int)(p - frm); +} + +#define NAN_MME_ELEMENT_ID 0x4C +/* MME: ElementID(1) + Length(1) + KeyID(2) + IPN(6) + MIC(8) = 18 */ +#define NAN_MME_LEN 18 + +uint32_t esp_nan_get_mme_len(void) +{ + return NAN_MME_LEN; +} + +/** + * @brief Construct dummy Management MIC Element (MME) + * + * Format: Element ID(1) + Length(1) + Key ID(2) + IPN/BIPN(6) + MIC(8) + * Key ID is set to 4, MIC is 8 bytes random (dummy). + */ +int esp_nan_construct_mme(uint8_t *frm, uint32_t ipn) +{ + if (!frm) { + return 0; + } + + uint8_t *p = frm; + + /* Element ID */ + *p++ = NAN_MME_ELEMENT_ID; + + /* Length: KeyID(2) + IPN(6) + MIC(8) = 16 */ + *p++ = 16; + + /* Key ID: 4 (LE16) */ + *p++ = 4; + *p++ = 0; + + /* IPN/BIPN: 6 bytes from ipn parameter (LE, zero-padded upper 2 bytes) */ + *p++ = (uint8_t)(ipn & 0xFF); + *p++ = (uint8_t)((ipn >> 8) & 0xFF); + *p++ = (uint8_t)((ipn >> 16) & 0xFF); + *p++ = (uint8_t)((ipn >> 24) & 0xFF); + *p++ = 0; + *p++ = 0; + + /* MIC: 8 bytes random (dummy) */ + os_get_random(p, 8); + p += 8; + + ESP_LOGI(TAG, "Constructed MME (dummy): len=%d, ipn=0x%lx", (int)(p - frm), (unsigned long)ipn); + return (int)(p - frm); +} + +#if defined(CONFIG_ESP_WIFI_NAN_PAIRING) && defined(CONFIG_ESP_WIFI_PASN_SUPPORT) && defined(CONFIG_ESP_WIFI_NAN_SECURITY) + +#include "crypto/sha256.h" +#include "utils/eloop.h" +#include "crypto/aes_wrap.h" +#include "common/ieee802_11_defs.h" +#include "common/wpa_common.h" + +#define NAN_PAIRING_FUP_MIN_ATTR_LEN 3 +#define NAN_PASN_KDE_OUI_TYPE_NIK 36 +#define NAN_PASN_KDE_OUI_TYPE_LIFETIME 37 +#define NAN_PASN_KEY_LIFETIME_NIK_BIT BIT(3) +#define NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC 86400U +#define NAN_ATTR_ID_SHARED_KEY_DESC 0x24 +#define GSP_SUBATTR_TRANSPORT_PORT 0x00 +#define GSP_SUBATTR_INSTANCE_NAME 0x03 +#define NAN_PAIRING_SRV_PORT 3333 +#define NAN_PAIRING_SRV_HOSTNAME "ESP-SRV-1234" +#define NAN_PAIRING_SSI_BUF_LEN 64 + +struct nan_pairing_fup_ctx { + uint8_t svc_id; + uint8_t peer_svc_id; + uint8_t peer_mac[MACADDR_LEN]; + size_t shared_key_attr_len; + uint8_t shared_key_attr[]; +}; + +static struct peer_svc_info *nan_find_peer_svc_exact(uint8_t own_svc_id, uint8_t peer_svc_id, + const uint8_t *peer_mac) +{ + struct peer_svc_info *temp; + + if (!own_svc_id || !peer_svc_id || !peer_mac) { + return NULL; + } + + for (int i = 0; i < ESP_WIFI_NAN_MAX_SVC_SUPPORTED; i++) { + struct own_svc_info *own = &s_nan_ctx.own_svc[i]; + + if (own->svc_id != own_svc_id) { + continue; + } + + SLIST_FOREACH(temp, &(own->peer_list), next) { + if (temp->svc_id == peer_svc_id && + memcmp(temp->peer_nmi, peer_mac, MACADDR_LEN) == 0) { + return temp; + } + } + break; + } + + return NULL; +} + +/** + * Build the WFA OUI + GSP protocol header that prefixes a Generic Service + * Protocol SSI (Wi-Fi Aware v4.0 §4.2.7). Ported from esp-nsd + * @c add_ssi_gsp_attr (wa_sd.c). + */ +static int nan_pairing_add_ssi_gsp_attr(uint8_t *buf) +{ + wifi_nan_wfa_ssi_t *wfa_ssi = (wifi_nan_wfa_ssi_t *)buf; + static const uint8_t oui_wfa[WIFI_OUI_LEN] = { 0x50, 0x6F, 0x9A }; + + memcpy(wfa_ssi->wfa_oui, oui_wfa, WIFI_OUI_LEN); + wfa_ssi->proto = WIFI_SVC_PROTO_GENERIC; + + return sizeof(wifi_nan_wfa_ssi_t); +} + +/** + * Append a single GSP sub-attribute (TLV: ID(1) | Length(2 LE) | Value). + * Ported from esp-nsd @c add_gsp_subattr (wa_sd.c). + */ +static int nan_pairing_add_gsp_subattr(uint8_t *buf, uint8_t sub_attr_id, + const void *payload, uint16_t len) +{ + uint8_t *p = buf; + + *p++ = sub_attr_id; + *((uint16_t *)p) = len; + p += sizeof(uint16_t); + memcpy(p, payload, len); + + return 1 + 2 + len; +} + +/** + * Build a GSP SSI carrying the SRV record (Transport Port + Instance Name). + * Ported from esp-nsd @c get_ssi_for_srv_record (wa_sd.c) with a stack + * buffer and a fixed (hostname, port) instead of dynamic allocation. + */ +static size_t nan_pairing_build_srv_ssi(uint8_t *buf, size_t buf_len, + const char *hostname, uint16_t port) +{ + uint8_t *p = buf; + size_t hostname_len; + size_t need; + + if (!buf || !hostname) { + return 0; + } + hostname_len = strlen(hostname); + need = sizeof(wifi_nan_wfa_ssi_t) + + (1 + 2 + sizeof(port)) + + (1 + 2 + hostname_len); + if (buf_len < need) { + return 0; + } + + p += nan_pairing_add_ssi_gsp_attr(p); + p += nan_pairing_add_gsp_subattr(p, GSP_SUBATTR_TRANSPORT_PORT, + &port, sizeof(port)); + p += nan_pairing_add_gsp_subattr(p, GSP_SUBATTR_INSTANCE_NAME, + hostname, hostname_len); + + return (size_t)(p - buf); +} + +/** + * Derive a NIRA tag for cipher version 0 (Wi-Fi Aware v4.0): + * Tag = Truncate-64(HMAC-SHA-256(NIK, "NIR" || NMI || Nonce)) + * + * Ported from hostap @c nan_crypto_derive_nira_tag (src/nan/nan_crypto.c), + * adapted to the ESP-IDF crypto trampoline. + */ +static int nan_pairing_derive_nira_tag(const uint8_t nik[NAN_PASN_NIK_LEN], + const uint8_t nmi_addr[ETH_ALEN], + const uint8_t nira_nonce[NAN_NIRA_NONCE_LEN], + uint8_t tag_out[NAN_NIRA_TAG_LEN]) +{ + const unsigned char *addr[3]; + int len_arr[3]; + uint8_t digest[32]; + + if (!nik || !nmi_addr || !nira_nonce || !tag_out) { + return -1; + } + if (!g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector) { + return -1; + } + + addr[0] = (const unsigned char *)NAN_NIRA_STR; + len_arr[0] = NAN_NIRA_STR_LEN; + addr[1] = nmi_addr; + len_arr[1] = ETH_ALEN; + addr[2] = nira_nonce; + len_arr[2] = NAN_NIRA_NONCE_LEN; + + if (g_wifi_default_wpa_crypto_funcs.hmac_sha256_vector(nik, NAN_PASN_NIK_LEN, + 3, addr, len_arr, + digest) != 0) { + return -1; + } + + memcpy(tag_out, digest, NAN_NIRA_TAG_LEN); + memset(digest, 0, sizeof(digest)); + return 0; +} + +/** + * Build a NIRA attribute (ID 0x2B) into @a buf using the NIK and our NMI. + * Returns total attribute length on success, 0 on failure. + */ +static size_t nan_pairing_build_nira_attr(uint8_t *buf, size_t buf_len, + const uint8_t nik[NAN_PASN_NIK_LEN], + const uint8_t nmi_addr[ETH_ALEN]) +{ + uint8_t nonce[NAN_NIRA_NONCE_LEN]; + uint8_t tag[NAN_NIRA_TAG_LEN]; + uint8_t *p = buf; + + if (!buf || !nik || !nmi_addr || buf_len < NAN_NIRA_ATTR_LEN) { + return 0; + } + if (os_get_random(nonce, sizeof(nonce)) != 0) { + return 0; + } + if (nan_pairing_derive_nira_tag(nik, nmi_addr, nonce, tag) != 0) { + return 0; + } + + *p++ = NAN_ATTR_ID_IDENTITY_RESOLUTION; + /* Attribute Length (LE16): CipherVersion(1) + Nonce(8) + Tag(8) = 17 */ + WPA_PUT_LE16(p, 1 + NAN_NIRA_NONCE_LEN + NAN_NIRA_TAG_LEN); + p += 2; + *p++ = NAN_NIRA_CIPHER_VER; + memcpy(p, nonce, NAN_NIRA_NONCE_LEN); + p += NAN_NIRA_NONCE_LEN; + memcpy(p, tag, NAN_NIRA_TAG_LEN); + p += NAN_NIRA_TAG_LEN; + + return (size_t)(p - buf); +} + +static size_t nan_pairing_build_plain_key_data(uint8_t *buf, size_t buf_len, + const uint8_t nik[NAN_PASN_NIK_LEN]) +{ + size_t pos = 0; + + if (!buf || !nik || buf_len < 40) { + return 0; + } + + /* NIK KDE: EID + Len + OUI + OUI Type + Cipher Ver + NIK. */ + buf[pos++] = WLAN_EID_VENDOR_SPECIFIC; + buf[pos++] = 4 + 1 + NAN_PASN_NIK_LEN; + buf[pos++] = 0x50; + buf[pos++] = 0x6f; + buf[pos++] = 0x9a; + buf[pos++] = NAN_PASN_KDE_OUI_TYPE_NIK; + buf[pos++] = 0; /* NAN_NIRA_CIPHER_VER_128 — matches hostap and iPhone */ + memcpy(&buf[pos], nik, NAN_PASN_NIK_LEN); + pos += NAN_PASN_NIK_LEN; + + /* Key Lifetime KDE: hostap's RX path rejects the frame when this KDE is + * missing (nan_pairing_followup_rx, "Key Lifetime KDE missing"). iPhone + * is likely the same. Layout: bitmap(LE16) | lifetime(BE32). */ + buf[pos++] = WLAN_EID_VENDOR_SPECIFIC; + buf[pos++] = 4 + 2 + 4; + buf[pos++] = 0x50; + buf[pos++] = 0x6f; + buf[pos++] = 0x9a; + buf[pos++] = NAN_PASN_KDE_OUI_TYPE_LIFETIME; + WPA_PUT_LE16(&buf[pos], NAN_PASN_KEY_LIFETIME_NIK_BIT); + pos += 2; + WPA_PUT_BE32(&buf[pos], NAN_PAIRING_DEFAULT_NIK_LIFETIME_SEC); + pos += 4; + + /* + * AES-WRAP requires plaintext length to be a multiple of 8. IEEE + * 802.11-2020 §12.7.2 specifies Key Data padding as a single 0xDD byte + * followed by zeros (not a sequence of well-formed vendor IEs). + */ + { + size_t pad = (8 - (pos % 8)) % 8; + if (pad) { + if (buf_len - pos < pad) { + return 0; + } + buf[pos++] = 0xDD; + while (--pad) { + buf[pos++] = 0x00; + } + } + } + + return pos; +} + +static esp_err_t nan_app_send_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, + const uint8_t *peer_mac, + const uint8_t *shared_key_attr, + size_t shared_key_attr_len) +{ + const struct nan_pasn_key_material *saved; + uint8_t plain[64] = {0}; + uint8_t wrapped[80] = {0}; + uint8_t key_desc[sizeof(struct wpa_eapol_key)] = {0}; + uint8_t shared_key_wrapped[sizeof(struct wpa_eapol_key) + sizeof(wrapped)] = {0}; + uint8_t nira_attr[NAN_NIRA_ATTR_LEN] = {0}; + uint8_t srv_ssi[NAN_PAIRING_SSI_BUF_LEN] = {0}; + uint8_t our_nmi[ETH_ALEN] = {0}; + uint8_t nik[NAN_PASN_NIK_LEN]; + size_t plain_len; + size_t wrapped_len; + size_t sk_attr_len; + size_t nira_len; + size_t srv_ssi_len; + uint16_t key_info; + uint8_t *w = shared_key_wrapped; + uint32_t tx_ctx = 0; + wifi_nan_followup_params_t fup = {0}; + extra_params_internal_t params_i = {0}; + + (void)shared_key_attr; + (void)shared_key_attr_len; + + if (!peer_mac || os_get_random(nik, sizeof(nik)) != 0) { + return ESP_ERR_INVALID_ARG; + } + + saved = nan_pasn_get_saved_keys(); + if (!saved || !saved->kek_len) { + ESP_LOGW(TAG, "Pairing follow-up: missing saved KEK"); + return ESP_ERR_INVALID_STATE; + } + + plain_len = nan_pairing_build_plain_key_data(plain, sizeof(plain), nik); + if (plain_len == 0 || plain_len % 8) { + return ESP_FAIL; + } + + if (plain_len / 8 > sizeof(wrapped) / 8 - 1) { + return ESP_ERR_INVALID_SIZE; + } + wrapped_len = plain_len + 8; + if (aes_wrap(saved->kek, saved->kek_len, plain_len / 8, plain, wrapped) != 0) { + return ESP_FAIL; + } + + key_desc[NAN_KEY_DESC_TYPE_OFF] = NAN_KEY_DESC_TYPE_RSN; + /* Key Info: AKM-defined | Pairwise | Install | ACK | Secure | Encrypted + * Key Data = 0x12C8. MIC bit deliberately *not* set — MIC computation is + * skipped here because we can only sign the SKDA bytes we own, while + * hostap (and iPhone) sign the entire assembled NAF body. Without that + * full-body assembly hook, an incorrect MIC is worse than none. */ + key_info = (uint16_t)(WPA_KEY_INFO_TYPE_AKM_DEFINED | + WPA_KEY_INFO_KEY_TYPE | + WPA_KEY_INFO_INSTALL | + WPA_KEY_INFO_ACK | + WPA_KEY_INFO_SECURE | + WPA_KEY_INFO_ENCR_KEY_DATA); + key_desc[NAN_KEY_DESC_KEY_INFO_OFF] = (uint8_t)((key_info >> 8) & 0xff); + key_desc[NAN_KEY_DESC_KEY_INFO_OFF + 1] = (uint8_t)(key_info & 0xff); + WPA_PUT_BE16(&key_desc[NAN_KEY_DESC_DATA_LEN_OFF], (uint16_t)wrapped_len); + + memcpy(w, key_desc, sizeof(key_desc)); + w += sizeof(key_desc); + memcpy(w, wrapped, wrapped_len); + w += wrapped_len; + + /* SSI: GSP (WFA OUI + proto=Generic) carrying an SRV record so iPhone + * has Transport Port + Instance Name to associate with this pairing. */ + srv_ssi_len = nan_pairing_build_srv_ssi(srv_ssi, sizeof(srv_ssi), + NAN_PAIRING_SRV_HOSTNAME, + NAN_PAIRING_SRV_PORT); + if (srv_ssi_len == 0) { + ESP_LOGW(TAG, "Pairing follow-up: failed to build SRV SSI"); + return ESP_FAIL; + } + + fup.inst_id = svc_id; + fup.peer_inst_id = peer_svc_id; + MACADDR_COPY(fup.peer_mac, peer_mac); + fup.ssi = srv_ssi; + fup.ssi_len = (uint16_t)srv_ssi_len; + + sk_attr_len = (size_t)(w - shared_key_wrapped); + params_i.pairing_npba = NULL; + params_i.shared_key_wrapped = shared_key_wrapped; + params_i.shared_key_wrapped_len = (uint16_t)sk_attr_len; + + /* NIRA proves possession of the NIK we just wrapped above; iPhone uses + * it to bind the NIK to the sender and won't commit the pairing record + * without it. */ + if (esp_wifi_get_mac(WIFI_IF_NAN, our_nmi) != ESP_OK) { + ESP_LOGW(TAG, "Pairing follow-up: cannot read NAN NMI for NIRA"); + return ESP_FAIL; + } + nira_len = nan_pairing_build_nira_attr(nira_attr, sizeof(nira_attr), + nik, our_nmi); + if (nira_len == 0) { + ESP_LOGW(TAG, "Pairing follow-up: NIRA attribute build failed"); + return ESP_FAIL; + } + params_i.nira_attr = nira_attr; + params_i.nira_attr_len = (uint16_t)nira_len; + + ESP_LOGI(TAG, "Pairing follow-up: sending key-desc payload to " MACSTR, MAC2STR(peer_mac)); + return esp_nan_internal_send_followup(&fup, &tx_ctx, ¶ms_i); +} + +static void nan_app_send_pairing_followup_eloop(void *eloop_data, void *user_data) +{ + struct nan_pairing_fup_ctx *ctx = (struct nan_pairing_fup_ctx *)user_data; + (void)eloop_data; + + if (!ctx) { + return; + } + (void) nan_app_send_pairing_followup(ctx->svc_id, ctx->peer_svc_id, + ctx->peer_mac, + ctx->shared_key_attr, + ctx->shared_key_attr_len); + os_free(ctx); +} + +void nan_app_receive_pairing_followup(uint8_t svc_id, uint8_t peer_svc_id, + const uint8_t *peer_mac, + const uint8_t *shared_key_attr) +{ + uint16_t attr_len; + uint16_t total_len; + uint8_t nik[NAN_APP_PEER_NIK_LEN]; + uint8_t cipher_ver = 0; + uint32_t lifetime_sec = 0; + struct nan_pairing_fup_ctx *ctx; + size_t alloc_len; + + if (!shared_key_attr || !peer_mac) { + return; + } + if (shared_key_attr[0] != NAN_ATTR_ID_SHARED_KEY_DESC) { + return; + } + + attr_len = shared_key_attr[1] | (shared_key_attr[2] << 8); + total_len = attr_len + 3; + if (total_len < NAN_PAIRING_FUP_MIN_ATTR_LEN) { + return; + } + if (nan_pasn_followup_decrypt_keys(shared_key_attr, total_len, + nik, sizeof(nik), + &cipher_ver, + &lifetime_sec) != 0) { + ESP_LOGW(TAG, "Pairing follow-up: failed to decrypt peer key data"); + return; + } + + NAN_DATA_LOCK(); + struct peer_svc_info *p_peer_svc = nan_find_peer_svc_exact(svc_id, peer_svc_id, peer_mac); + if (p_peer_svc) { + memcpy(p_peer_svc->peer_nik, nik, NAN_APP_PEER_NIK_LEN); + p_peer_svc->peer_nik_cipher_ver = cipher_ver; + p_peer_svc->peer_nik_lifetime_sec = lifetime_sec; + p_peer_svc->has_nik = true; + ESP_LOGI(TAG, "Stored peer NIK from " MACSTR " (cipher_ver=%u, lifetime=%u s)", + MAC2STR(peer_mac), cipher_ver, lifetime_sec); + ESP_LOG_BUFFER_HEXDUMP("NIK", nik, NAN_APP_PEER_NIK_LEN, ESP_LOG_INFO); + } + NAN_DATA_UNLOCK(); + + alloc_len = sizeof(*ctx) + total_len; + ctx = os_zalloc(alloc_len); + if (!ctx) { + return; + } + ctx->svc_id = svc_id; + ctx->peer_svc_id = peer_svc_id; + MACADDR_COPY(ctx->peer_mac, peer_mac); + ctx->shared_key_attr_len = total_len; + memcpy(ctx->shared_key_attr, shared_key_attr, total_len); + + if (eloop_register_timeout(0, 0, nan_app_send_pairing_followup_eloop, NULL, ctx) != 0) { + os_free(ctx); + ESP_LOGW(TAG, "Pairing follow-up: failed to schedule response"); + } +} + +#endif /* CONFIG_ESP_WIFI_NAN_PAIRING && CONFIG_ESP_WIFI_PASN_SUPPORT && CONFIG_ESP_WIFI_NAN_SECURITY */ + +#endif /* CONFIG_ESP_WIFI_NAN_PAIRING */ diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c index 69cebdac9ad..9698d340d82 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_security.c @@ -94,6 +94,11 @@ static const uint8_t *nan_find_attr(const uint8_t *attrs, size_t attrs_len, return NULL; } +static bool nan_csid_bitmap_has_pasn(uint16_t csid_bitmap) +{ + return (csid_bitmap & WIFI_NAN_CSID_BIT_NCS_PK_PASN_128) != 0; +} + /* * Service ID = first 6 bytes of SHA256(lowercase(service_name)) * per Wi-Fi Aware v4.0 §5.1.5 (Service Name and Service ID). @@ -795,6 +800,27 @@ bool nan_security_service_match(const struct own_svc_info *own_svc, if (!own_svc || !peer_svc || !publisher_nmi || !peer_sec) { return false; } + + const wifi_nan_discovery_security_params_t *cfg = &own_svc->user_cfg; + + if (cfg->num_credentials == 0 || + cfg->num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) { + return false; + } + + if (nan_csid_bitmap_has_pasn(peer_sec->csid_bitmap)) { + int c; + for (c = 0; c < cfg->num_credentials; c++) { + const wifi_nan_credential_t *cred = &cfg->creds[c]; + if (peer_sec->csid_bitmap & (1 << cred->csid)) { + return true; + } + } + if (c == cfg->num_credentials) { + return false; + } + } + if (peer_sec->num_pmkids == 0) { return false; } @@ -805,12 +831,6 @@ bool nan_security_service_match(const struct own_svc_info *own_svc, return false; } - const wifi_nan_discovery_security_params_t *cfg = &own_svc->user_cfg; - if (cfg->num_credentials == 0 || - cfg->num_credentials > ESP_WIFI_NAN_MAX_CREDS_PER_SVC) { - return false; - } - uint8_t i_addr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; const unsigned char *addr_pmkid[4] = {(const unsigned char *)NAN_PMK_NAME_LABEL, i_addr, publisher_nmi, service_id diff --git a/components/wpa_supplicant/CMakeLists.txt b/components/wpa_supplicant/CMakeLists.txt index 9a92fdf0088..aa981ef0e65 100644 --- a/components/wpa_supplicant/CMakeLists.txt +++ b/components/wpa_supplicant/CMakeLists.txt @@ -86,7 +86,7 @@ set(esp_srcs "esp_supplicant/src/esp_eap_client.c" "esp_supplicant/src/esp_wps.c" "esp_supplicant/src/esp_wpa3.c" "esp_supplicant/src/esp_owe.c" - "esp_supplicant/src/nan_pasn.c") + "esp_supplicant/src/esp_nan_supplicant.c") if(CONFIG_ESP_WIFI_SOFTAP_SUPPORT) set(esp_srcs ${esp_srcs} "esp_supplicant/src/esp_hostap.c") endif() diff --git a/components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h b/components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h new file mode 100644 index 00000000000..fd68e17a371 --- /dev/null +++ b/components/wpa_supplicant/esp_supplicant/include/esp_private/esp_supp_nan.h @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Private bridge API between the WiFi driver / NAN discovery engine + * (components/esp_wifi/wifi_apps/nan_app) and the imported NAN + * NDP/NDL/bootstrap/security engine living in + * components/wpa_supplicant/src/nan/. + * + * Symbols declared here are implemented in + * components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c. + */ + +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "esp_wifi_types_generic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct nan_data; +typedef void (*esp_nan_pairing_key_installed_cb_t)(const uint8_t *peer_nmi); + +#if CONFIG_ESP_WIFI_PASN_SUPPORT +#ifndef ETH_ALEN +#define ETH_ALEN 6 +#endif +#ifndef NAN_PASN_GLOBAL_PTK_BLOB_MAX +#define NAN_PASN_GLOBAL_PTK_BLOB_MAX 128 +#endif +#ifndef NAN_PASN_KEY_PMK_MAX +#define NAN_PASN_KEY_PMK_MAX 64 +#endif +#ifndef NAN_PASN_KEK_MAX_LEN +#define NAN_PASN_KEK_MAX_LEN 32 +#endif +#ifndef NAN_PASN_NIK_LEN +#define NAN_PASN_NIK_LEN 16 +#endif + +enum nan_role { + NAN_ROLE_IDLE = 0, + NAN_ROLE_PAIRING_INITIATOR = 1, + NAN_ROLE_PAIRING_RESPONDER = 2, +}; + +/** + * Last PASN key material after successful pairing (PMK + flattened PTK KCK|KEK|TK|KDK). + * Written before @c pasn PTK is cleared; initiator session is torn down on Auth3 TX status. + */ +struct nan_pasn_key_material { + uint8_t valid; + uint8_t peer_addr[ETH_ALEN]; + enum nan_role role; + int akmp; + int cipher; + size_t pmk_len; + uint8_t pmk[NAN_PASN_KEY_PMK_MAX]; + size_t ptk_blob_len; + uint8_t ptk_blob[NAN_PASN_GLOBAL_PTK_BLOB_MAX]; + size_t kek_len; + uint8_t kek[NAN_PASN_KEK_MAX_LEN]; +}; + +/** + * @brief Schedule NAN PASN responder setup after pairing bootstrapping indication. + * + * Invoked from @c nan_app_pairing_indication_cb when the local device is the + * pairing responder. Runs on the wpa_supplicant eloop thread. + * + * @param peer_nmi Peer NMI (6 bytes). + * @param pincode 6-digit PIN (0..999999), or @c UINT32_MAX for the default PIN. + * @param pairing_key_installed_cb Callback invoked after pairwise key installation with peer NMI. + * @return 0 on success, -1 on failure. + */ +int esp_nan_supp_pasn_responder_init(const uint8_t *peer_nmi, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb); + +/** + * @brief Schedule NAN PASN initiator authentication after pairing bootstrapping confirm. + * + * Invoked from @c nan_app_pairing_confirm_cb when the local device is the + * pairing initiator and bootstrapping completed successfully (@a status == 0). + * Runs on the wpa_supplicant eloop thread. + * + * @param peer_nmi Peer NMI (6 bytes). + * @param pincode 6-digit PIN (0..999999), or @c UINT32_MAX for the default PIN. + * @param pairing_key_installed_cb Callback invoked after pairwise key installation with peer NMI. + * @return 0 on success, -1 on failure. + */ +int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb); + +/** + * Schedule @ref handle_auth_pasn from NAN app callback table. + */ +void handle_auth_pasn(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t status); + +const struct nan_pasn_key_material *nan_pasn_get_saved_keys(void); +void nan_pasn_clear_saved_keys(void); + +/** + * Decrypt a NAN Shared Key Descriptor attribute received in a follow-up frame. + * + * @param[in] shared_key_attr Raw attribute buffer beginning with the + * NAN Shared Key Descriptor ID octet. + * @param[in] attr_total_len Total length including the 3-byte attribute header. + * @param[out] nik Buffer receiving the decrypted NIK. + * @param[in] nik_size Size of @a nik. Must be at least @c NAN_PASN_NIK_LEN. + * @param[out] cipher_ver Optional. Receives the NIK cipher version. + * @param[out] lifetime_sec Optional. Receives the NIK lifetime in seconds. + * @return 0 on success, -1 on failure. + */ +int nan_pasn_followup_decrypt_keys(const uint8_t *shared_key_attr, + size_t attr_total_len, + uint8_t *nik, size_t nik_size, + uint8_t *cipher_ver, + uint32_t *lifetime_sec); + +#endif /* CONFIG_ESP_WIFI_PASN_SUPPORT */ + +#ifdef __cplusplus +} +#endif diff --git a/components/wpa_supplicant/esp_supplicant/include/nan_pasn.h b/components/wpa_supplicant/esp_supplicant/include/nan_pasn.h deleted file mode 100644 index 2ed5e83e5e3..00000000000 --- a/components/wpa_supplicant/esp_supplicant/include/nan_pasn.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - * - * NAN PASN — public API and types for NSD / Wi-Fi Aware examples (e.g. esp-nsd udp_client). - * When CONFIG_ESP_WIFI_PASN_SUPPORT is off, nan_pasn.c still links stubs (no-op / -1) - * for this API so apps can call e.g. pasn_responder_init_eloop without a link error. - */ - -#ifndef _NAN_PASN_H_ -#define _NAN_PASN_H_ - -#include -#include -#include - -/* Self-contained: public consumers only get esp_supplicant/include. */ -#ifndef ETH_ALEN -#define ETH_ALEN 6 -#endif -#ifndef PMKID_LEN -#define PMKID_LEN 16 -#endif -#ifndef NAN_PASN_GLOBAL_PTK_BLOB_MAX -#define NAN_PASN_GLOBAL_PTK_BLOB_MAX 128 -#endif -#ifndef NAN_PASN_KEY_PMK_MAX -#define NAN_PASN_KEY_PMK_MAX 64 -#endif - -struct wpabuf; -struct pasn_data; -struct rsn_pmksa_cache; - -enum nan_role { - NAN_ROLE_IDLE = 0, - NAN_ROLE_PAIRING_INITIATOR = 1, - NAN_ROLE_PAIRING_RESPONDER = 2, -}; - -struct nan_config { - uint8_t dev_addr[ETH_ALEN]; - uint8_t pasn_type; - void *cb_ctx; - int (*set_pmksa)(void *ctx, const uint8_t *peer_addr, const uint8_t *pmkid); - int (*pasn_send_mgmt)(void *ctx, const uint8_t *data, size_t data_len, int noack, - unsigned int freq, unsigned int wait_ms); - int (*prepare_data_element)(void *ctx, const uint8_t *peer_addr); - int (*parse_data_element)(void *ctx, const uint8_t *data, size_t len); - int (*pasn_validate_pmkid)(void *ctx, const uint8_t *addr, const uint8_t *pmkid); -}; - -struct nan_pasn_data { - enum nan_role dev_role; - /** Last known unicast peer; used when @c addr is broadcast so PASN Auth1 DA is not ff:ff:ff:ff:ff:ff. */ - uint8_t pasn_unicast_peer[ETH_ALEN]; - /** - * NUL-terminated decimal PIN ('0'–'9' only). - * Same buffer is SAE password material (ASCII per digit) and @c pasn->password. - */ - char dev_sae_pin[64]; - size_t dev_sae_pin_len; - struct nan_config *cfg; - struct rsn_pmksa_cache *initiator_pmksa; - struct rsn_pmksa_cache *responder_pmksa; - uint8_t pasn_ptk[128]; - size_t pasn_ptk_len; - struct pasn_data *pasn; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -int nan_initiate_pasn_verify(struct nan_pasn_data *pd, const uint8_t *peer_addr, - int freq, int role, const uint8_t *bssid, - const uint8_t *ssid, size_t ssid_len); - -int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const uint8_t *addr, int freq); - -struct nan_pasn_data *nan_pasn_data_init(void); -void nan_pasn_data_deinit(struct nan_pasn_data *pd); -int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, int freq); -int nan_pasn_auth(struct nan_pasn_data **pd_out, const uint8_t *peer_addr, int freq); -/** - * Defer PASN auth initiation on eloop (same delayed pattern as @ref pasn_responder_init_eloop). - * Replaces the global NAN PASN object: deinits any existing @c esp_nan_app_get_pasn_data(), - * @ref nan_pasn_data_init, optional PIN override, then @ref nan_pasn_auth_initiate. - * Operating frequency is chosen internally (current NAN channel, or 2412 MHz fallback). - * @param pincode 6-digit PIN value (0..999999), e.g. @c 0 for @c "000000". @c UINT32_MAX to keep the default PIN from @ref nan_pasn_data_init. - */ -int nan_pasn_auth_eloop(const uint8_t *peer_addr, uint32_t pincode); - -/** - * Schedule @ref nan_initiate_pasn_verify on wpa_supplicant eloop after @a secs / @a usecs. - * Looks up @c struct nan_pasn_data via @ref esp_nan_app_get_pasn_data in the callback. - * @a bssid may be NULL to use @a peer_addr as BSSID. @a ssid may be NULL if @a ssid_len is 0. - */ -int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs, - const uint8_t *peer_addr, int freq, int role, - const uint8_t *bssid, - const uint8_t *ssid, size_t ssid_len); - -/** - * NAN PASN responder setup (nan_pasn_data_init, esp_nan_app_set_pasn_data, PIN, nan_pasn_initialize). - * @param pincode 6-digit PIN value (e.g. @c wa_pairing_cred_t.pincode, 0..999999). Use @c UINT32_MAX to keep the default PIN from @ref nan_pasn_data_init (no override). - * Frequency is chosen internally (current NAN channel, or 2412 MHz fallback). - */ -int pasn_responder_init(const uint8_t *peer_addr, uint32_t pincode); -/** - * Schedule @ref pasn_responder_init on wpa_supplicant eloop (delay 0). - * @a peer_addr may be NULL (broadcast placeholder). - * @a pincode same as @ref pasn_responder_init (value is stored in eloop context). - */ -int pasn_responder_init_eloop(const uint8_t *peer_addr, uint32_t pincode); - -void handle_auth_pasn(uint8_t *buf, size_t len, uint16_t trans_seq, uint16_t status); - -/** - * Last PASN key material after successful pairing (PMK + flattened PTK KCK|KEK|TK|KDK). - * Written before @c pasn PTK is cleared; initiator session is torn down on Auth3 TX status. - */ -struct nan_pasn_key_material { - uint8_t valid; - uint8_t peer_addr[ETH_ALEN]; - enum nan_role role; - int akmp; - int cipher; - size_t pmk_len; - uint8_t pmk[NAN_PASN_KEY_PMK_MAX]; - size_t ptk_blob_len; - uint8_t ptk_blob[NAN_PASN_GLOBAL_PTK_BLOB_MAX]; -}; - -const struct nan_pasn_key_material *nan_pasn_get_saved_keys(void); -void nan_pasn_clear_saved_keys(void); - -#ifdef __cplusplus -} -#endif - -#endif /* _NAN_PASN_H_ */ diff --git a/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls-ec.c b/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls-ec.c index 4cccdeed481..93b6311395f 100644 --- a/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls-ec.c +++ b/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls-ec.c @@ -3042,8 +3042,10 @@ mbedtls_ecp_group_id ecc_group_from_psa(psa_ecc_family_t family, switch (family) { case PSA_ECC_FAMILY_SECP_R1: switch (bits) { +#ifdef MBEDTLS_ECP_DP_SECP192R1_ENABLED case 192: return MBEDTLS_ECP_DP_SECP192R1; +#endif case 256: return MBEDTLS_ECP_DP_SECP256R1; case 384: @@ -3075,8 +3077,10 @@ mbedtls_ecp_group_id ecc_group_from_psa(psa_ecc_family_t family, case PSA_ECC_FAMILY_SECP_K1: switch (bits) { +#ifdef MBEDTLS_ECP_DP_SECP192K1_ENABLED case 192: return MBEDTLS_ECP_DP_SECP192K1; +#endif case 256: return MBEDTLS_ECP_DP_SECP256K1; } diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h new file mode 100644 index 00000000000..61bb80cf7c0 --- /dev/null +++ b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supp_i.h @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "esp_private/esp_supp_nan.h" + +struct wpabuf; +struct pasn_data; +struct rsn_pmksa_cache; + +typedef void (*nan_pasn_pairing_key_installed_cb_t)(const uint8_t *peer_nmi); + +struct nan_config { + uint8_t dev_addr[ETH_ALEN]; + uint8_t pasn_type; + void *cb_ctx; + int (*set_pmksa)(void *ctx, const uint8_t *peer_addr, const uint8_t *pmkid); + int (*pasn_send_mgmt)(void *ctx, const uint8_t *data, size_t data_len, int noack, + unsigned int freq, unsigned int wait_ms); + int (*prepare_data_element)(void *ctx, const uint8_t *peer_addr); + int (*parse_data_element)(void *ctx, const uint8_t *data, size_t len); + int (*pasn_validate_pmkid)(void *ctx, const uint8_t *addr, const uint8_t *pmkid); +}; + +struct nan_pasn_data { + enum nan_role dev_role; + uint8_t pasn_unicast_peer[ETH_ALEN]; + char dev_sae_pin[64]; + size_t dev_sae_pin_len; + struct nan_config *cfg; + struct rsn_pmksa_cache *initiator_pmksa; + struct rsn_pmksa_cache *responder_pmksa; + uint8_t pasn_ptk[128]; + size_t pasn_ptk_len; + struct pasn_data *pasn; + nan_pasn_pairing_key_installed_cb_t pairing_key_installed_cb; +}; + +int nan_initiate_pasn_verify(struct nan_pasn_data *pd, const uint8_t *peer_addr, + int freq, int role, const uint8_t *bssid, + const uint8_t *ssid, size_t ssid_len); +int nan_initiate_pasn_auth(struct nan_pasn_data *pd, const uint8_t *addr, int freq); +struct nan_pasn_data *nan_pasn_data_init(void); +void nan_pasn_data_deinit(struct nan_pasn_data *pd); +int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, int freq); +int nan_pasn_auth(struct nan_pasn_data **pd_out, const uint8_t *peer_addr, int freq); +int nan_pasn_verify_eloop(unsigned int secs, unsigned int usecs, + const uint8_t *peer_addr, int freq, int role, + const uint8_t *bssid, + const uint8_t *ssid, size_t ssid_len); diff --git a/components/wpa_supplicant/esp_supplicant/src/nan_pasn.c b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c similarity index 60% rename from components/wpa_supplicant/esp_supplicant/src/nan_pasn.c rename to components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c index 8a96f541545..dfc9cd3da28 100644 --- a/components/wpa_supplicant/esp_supplicant/src/nan_pasn.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_nan_supplicant.c @@ -7,7 +7,8 @@ */ #include "sdkconfig.h" -#include "nan_pasn.h" +#include "esp_private/esp_supp_nan.h" +#include "esp_nan_supp_i.h" #if CONFIG_ESP_WIFI_PASN_SUPPORT @@ -17,6 +18,9 @@ #include "common/nan.h" #include "esp_wifi_driver.h" #include "crypto/crypto.h" +#include "crypto/aes_wrap.h" +#include "crypto/sha256.h" +#include "crypto/sha384.h" #include "pasn/pasn_common.h" #include "common/wpa_common.h" @@ -27,6 +31,7 @@ #include "utils/eloop.h" #include "esp_err.h" #include "esp_wifi.h" +#include "esp_event.h" #include "esp_private/wifi.h" #define IEEE80211_MGMT_HDRLEN 24 @@ -46,6 +51,135 @@ static struct nan_pasn_key_material g_nan_pasn_saved_keys; /* Key index for esp_wifi_set_nan_key_internal (NAN PASN pairwise TK). */ int temp = 1; +#define NAN_PASN_AES_WRAP_OVERHEAD 8 +#define NAN_PASN_AES_WRAP_MIN_CIPHERTEXT (NAN_PASN_AES_WRAP_OVERHEAD + 8) + +/** + * Key lengths for NCS-PK-PASN-128 / NCS-PK-PASN-256 (Wi-Fi Aware pairing). + * Mirrors hostap @c nan_crypto_cipher_*_len in src/nan/nan_crypto.c. + */ +static size_t nan_pasn_cipher_kck_len(int cipher) +{ + switch (cipher) { + case WPA_CIPHER_CCMP: + return 16; + case WPA_CIPHER_GCMP_256: + return 24; + default: + return 0; + } +} + +static size_t nan_pasn_cipher_kek_len(int cipher) +{ + switch (cipher) { + case WPA_CIPHER_CCMP: + return 16; + case WPA_CIPHER_GCMP_256: + return 32; + default: + return 0; + } +} + +static size_t nan_pasn_cipher_tk_len(int cipher) +{ + switch (cipher) { + case WPA_CIPHER_CCMP: + return 16; + case WPA_CIPHER_GCMP_256: + return 32; + default: + return 0; + } +} + +static size_t nan_pasn_cipher_mic_len(int cipher) +{ + switch (cipher) { + case WPA_CIPHER_CCMP: + return 16; + case WPA_CIPHER_GCMP_256: + return 24; + default: + return 0; + } +} + +static size_t nan_pasn_cipher_eapol_key_hdrlen(int cipher) +{ + size_t mic_len = nan_pasn_cipher_mic_len(cipher); + + if (mic_len == 24) { + return sizeof(struct wpa_eapol_key_192); + } + if (mic_len == 16) { + return sizeof(struct wpa_eapol_key); + } + return 0; +} + +static bool nan_pasn_cipher_is_supported(int cipher) +{ + return cipher == WPA_CIPHER_CCMP || cipher == WPA_CIPHER_GCMP_256; +} + +/** + * nan_crypto_derive_from_kdk - Derive a key from KDK using KDF-HASH-NNN + * + * KEY = KDF-HASH-NNN(KDK, label, Pairing Initiator NMI || Pairing Responder NMI) + * + * Mirrors hostap @c nan_crypto_derive_from_kdk (src/nan/nan_crypto.c). + * @a cipher is @c WPA_CIPHER_CCMP (NCS-PK-PASN-128) or @c WPA_CIPHER_GCMP_256 + * (NCS-PK-PASN-256). + */ +static int nan_crypto_derive_from_kdk(const u8 *kdk, size_t kdk_len, int cipher, + const char *label, + const u8 *initiator_nmi, + const u8 *responder_nmi, + u8 *key, size_t key_len) +{ + u8 data[ETH_ALEN * 2]; + int ret; + + if (!kdk || !kdk_len || !label || !initiator_nmi || !responder_nmi || + !key || !key_len) { + wpa_printf(MSG_INFO, + "NAN: Invalid parameters for NPK/KEK derivation"); + return -1; + } + + os_memcpy(data, initiator_nmi, ETH_ALEN); + os_memcpy(data + ETH_ALEN, responder_nmi, ETH_ALEN); + + if (cipher == WPA_CIPHER_CCMP) { + ret = sha256_prf(kdk, kdk_len, label, data, sizeof(data), key, key_len); + } else if (cipher == WPA_CIPHER_GCMP_256) { + ret = sha384_prf(kdk, kdk_len, label, data, sizeof(data), key, key_len); + } else { + wpa_printf(MSG_INFO, + "NAN: Unsupported cipher suite for key derivation: %d", + cipher); + return -1; + } + + if (ret) { + wpa_printf(MSG_INFO, + "NAN: NPK/KEK derivation failed (ret=%d)", ret); + return ret; + } + + wpa_hexdump_key(MSG_DEBUG, "NAN: KDK", kdk, kdk_len); + wpa_printf(MSG_DEBUG, "NAN: Label: %s", label); + wpa_printf(MSG_DEBUG, "NAN: Initiator NMI " MACSTR, + MAC2STR(initiator_nmi)); + wpa_printf(MSG_DEBUG, "NAN: Responder NMI " MACSTR, + MAC2STR(responder_nmi)); + wpa_hexdump_key(MSG_DEBUG, "NAN: Derived key", key, key_len); + + return 0; +} + /** Same layout as @ref nan_pasn_store_ptk (KCK|KEK|TK|KDK). */ static int nan_pasn_flatten_ptk_blob(struct wpa_ptk *ptk, u8 *dst, size_t dst_sz, size_t *out_len) @@ -83,38 +217,40 @@ static int nan_pasn_flatten_ptk_blob(struct wpa_ptk *ptk, u8 *dst, size_t dst_sz * Install PASN pairwise TK into the NAN interface key table (firmware). * Call while @a pasn still holds a valid PTK (before forced_memzero). */ -static void nan_pasn_install_nan_pairwise_tk(struct pasn_data *pasn) +static int nan_pasn_install_nan_pairwise_tk(struct nan_pasn_data *nan, struct pasn_data *pasn) { struct wpa_ptk *ptk; uint8_t key_rsc[8] = {0}; - uint8_t peer[ETH_ALEN]; int kret; + (void)nan; if (!pasn) { - return; + return -1; } - if (pasn->cipher != WPA_CIPHER_CCMP) { + if (!nan_pasn_cipher_is_supported(pasn->cipher)) { wpa_printf(MSG_INFO, "NAN PASN: skip NAN TK install (cipher=%d)", pasn->cipher); - return; + return -1; } ptk = pasn_get_ptk(pasn); - if (!ptk || !ptk->tk_len || ptk->tk_len > sizeof(ptk->tk)) { - return; + if (!ptk || !ptk->tk_len || ptk->tk_len != nan_pasn_cipher_tk_len(pasn->cipher) || + ptk->tk_len > sizeof(ptk->tk)) { + return -1; } - os_memcpy(peer, pasn->peer_addr, ETH_ALEN); - wpa_hexdump_key(MSG_INFO, "NAN PASN: TK before esp_wifi_set_nan_key_internal", - ptk->tk, ptk->tk_len); + ESP_LOG_BUFFER_HEXDUMP("NAN PASN: NM-TK", + ptk->tk, ptk->tk_len, ESP_LOG_INFO); kret = esp_wifi_set_nan_key_internal( - NAN_PASN_WIFI_ALG_CCMP, peer, temp, 1, key_rsc, sizeof(key_rsc), + NAN_PASN_WIFI_ALG_CCMP, pasn->peer_addr, temp, 1, key_rsc, sizeof(key_rsc), ptk->tk, ptk->tk_len, - NAN_PASN_KEY_FLAG_PAIRWISE | NAN_PASN_KEY_FLAG_RX | NAN_PASN_KEY_FLAG_TX); + NAN_KEY_NM_TK); if (kret != 0) { wpa_printf(MSG_WARNING, "NAN PASN: esp_wifi_set_nan_key_internal failed (%d)", kret); + return -1; } + return 0; } /** @@ -150,47 +286,48 @@ static void nan_pasn_post_pasn_pairing_indication_evt(struct nan_pasn_data *nan, } /** - * Common path for @ref esp_nan_app_post_pasn_pairing_confirm (initiator and responder). + * nan_crypto_derive_kek - Derive KEK from NM-KDK after PASN pairing * - * @param auth_frame_status_code IEEE 802.11 Authentication @c status_code from the RX frame (host endian). - * @param initiator_nmi Initiator NMI (6 octets). - * @param responder_nmi Responder NMI (6 octets). - * @param require_pasn_internal_success If true, post only when @c pasn->status is @c WLAN_STATUS_SUCCESS - * (initiator after Auth2). Responder uses false. + * NM-KEK = KDF-HASH-MMM(NM-KDK, "NAN Management KEK Derivation", + * Pairing Initiator NMI || Pairing Responder NMI) + * + * Mirrors hostap @c nan_crypto_derive_kek (src/nan/nan_crypto.c). Called from + * @ref nan_pasn_copy_keys_from_pasn when @c ptk->kdk_len is set. */ -static void nan_pasn_post_pasn_pairing_confirm_evt(struct nan_pasn_data *nan, - struct pasn_data *pasn, - u16 auth_frame_status_code, - const u8 initiator_nmi[ETH_ALEN], - const u8 responder_nmi[ETH_ALEN], - bool require_pasn_internal_success) +static int nan_crypto_derive_kek(const u8 *kdk, size_t kdk_len, int cipher, + const u8 *initiator_nmi, + const u8 *responder_nmi, struct wpa_ptk *ptk) { -#if 0 - wifi_event_nan_pasn_pairing_confirm_t conf; + const char *label = "NAN Management KEK Derivation"; + size_t kek_len; - if (!nan || !pasn) { - return; - } - if (require_pasn_internal_success && pasn->status != WLAN_STATUS_SUCCESS) { - return; + wpa_printf(MSG_DEBUG, "NAN: Deriving KEK from NM-KDK"); + + if (!kdk || !kdk_len || !initiator_nmi || !responder_nmi || !ptk) { + wpa_printf(MSG_INFO, + "NAN: Invalid parameters for KEK derivation"); + return -1; } - os_memset(&conf, 0, sizeof(conf)); - conf.type = WIFI_NAN_PASN_PAIRING_IND_TYPE_SETUP; - conf.status = WIFI_NAN_PASN_PAIRING_CONFIRM_STATUS_ACCEPTED; - conf.self_handle = 0; - conf.reason_code = - (auth_frame_status_code == WLAN_STATUS_SUCCESS) ? 0 - : (uint8_t)auth_frame_status_code; - os_memcpy(conf.initiator_nan_address, initiator_nmi, ETH_ALEN); - os_memcpy(conf.responder_nan_address, responder_nmi, ETH_ALEN); - conf.paired_peer_handle_valid = 0; - conf.auth_password = nan->dev_sae_pin_len > 0 ? 1 : 0; - conf.auth_opportunistic = - (pasn->akmp == WPA_KEY_MGMT_PASN && nan->dev_sae_pin_len == 0) ? 1 : 0; - conf.npk_nik_caching = 0; - esp_nan_app_post_pasn_pairing_confirm(&conf); -#endif + if (!nan_pasn_cipher_is_supported(cipher)) { + wpa_printf(MSG_INFO, + "NAN: Unsupported cipher suite for KEK derivation: %d", + cipher); + return -1; + } + + kek_len = nan_pasn_cipher_kek_len(cipher); + if (kek_len > sizeof(ptk->kek)) { + wpa_printf(MSG_INFO, + "NAN: KEK length %zu exceeds wpa_ptk buffer", kek_len); + return -1; + } + + ptk->kek_len = kek_len; + + return nan_crypto_derive_from_kdk(kdk, kdk_len, cipher, label, + initiator_nmi, responder_nmi, + ptk->kek, ptk->kek_len); } static void nan_pasn_copy_keys_from_pasn(struct nan_pasn_data *nan, struct pasn_data *pasn) @@ -222,6 +359,34 @@ static void nan_pasn_copy_keys_from_pasn(struct nan_pasn_data *nan, struct pasn_ g_nan_pasn_saved_keys.pmk_len = pmk_len; } + /* + * NAN Management KEK is derived from KDK using pairing initiator/responder + * NMI addresses (nan_crypto_derive_kek), same pattern as hostap. ND-PMK is + * derived earlier via pasn_nd_pmk_derive_from_kdk_store() (hostap + * nan_crypto_derive_nd_pmk_from_kdk) after pasn_pmk_to_ptk. + */ + if (ptk->kdk_len) { + const u8 *initiator_nmi; + const u8 *responder_nmi; + + if (nan->dev_role == NAN_ROLE_PAIRING_INITIATOR) { + initiator_nmi = pasn->own_addr; + responder_nmi = pasn->peer_addr; + } else { + initiator_nmi = pasn->peer_addr; + responder_nmi = pasn->own_addr; + } + + if (nan_crypto_derive_kek(ptk->kdk, ptk->kdk_len, pasn->cipher, + initiator_nmi, responder_nmi, ptk) != 0) { + wpa_printf(MSG_INFO, "NAN PASN: KEK derivation failed"); + forced_memzero(&g_nan_pasn_saved_keys, sizeof(g_nan_pasn_saved_keys)); + return; + } + + ptk->ptk_len = ptk->kck_len + ptk->kek_len + ptk->tk_len + ptk->kdk_len; + } + if (nan_pasn_flatten_ptk_blob(ptk, g_nan_pasn_saved_keys.ptk_blob, sizeof(g_nan_pasn_saved_keys.ptk_blob), &g_nan_pasn_saved_keys.ptk_blob_len) != 0) { @@ -229,6 +394,11 @@ static void nan_pasn_copy_keys_from_pasn(struct nan_pasn_data *nan, struct pasn_ return; } + if (ptk->kek_len && ptk->kek_len <= sizeof(g_nan_pasn_saved_keys.kek)) { + os_memcpy(g_nan_pasn_saved_keys.kek, ptk->kek, ptk->kek_len); + g_nan_pasn_saved_keys.kek_len = ptk->kek_len; + } + g_nan_pasn_saved_keys.valid = 1; } @@ -242,6 +412,336 @@ void nan_pasn_clear_saved_keys(void) forced_memzero(&g_nan_pasn_saved_keys, sizeof(g_nan_pasn_saved_keys)); } +/* NAN KDE OUI Type values from Wi-Fi Aware spec v4.0, Table 126. */ +#define NAN_PASN_KDE_OUI_TYPE_NIK 36 +#define NAN_PASN_KDE_OUI_TYPE_LIFETIME 37 +#define NAN_PASN_KEY_LIFETIME_NIK_BIT BIT(3) + +/** + * Decrypt NAN key data using AES Key Unwrap (RFC 3394). + * + * Ported from hostap @c nan_crypto_decrypt_key_data (src/nan/nan_crypto.c). + * Caller is responsible for freeing the returned wpabuf using @c wpabuf_free. + * + */ + +static struct wpabuf * +nan_crypto_decrypt_key_data(const u8 *kek, size_t kek_len, + const u8 *encrypted_data, size_t encrypted_len) +{ + struct wpabuf *decrypted; + size_t plain_len; + u8 *buf; + + if (!encrypted_data || !encrypted_len) { + wpa_printf(MSG_INFO, "NAN: Invalid encrypted key data"); + return NULL; + } + + wpa_hexdump_key(MSG_DEBUG, "NAN: Encrypted key data", + encrypted_data, encrypted_len); + + if (!kek || !kek_len) { + wpa_printf(MSG_INFO, + "NAN: No KEK available for key data decryption"); + return NULL; + } + + wpa_hexdump_key(MSG_DEBUG, "NAN: KEK for decryption", kek, kek_len); + + /* AES-WRAP adds 8 bytes overhead and requires 8-byte aligned input. */ + if (encrypted_len < NAN_PASN_AES_WRAP_MIN_CIPHERTEXT || + encrypted_len % 8 != 0) { + wpa_printf(MSG_INFO, + "NAN: Invalid encrypted key data length %zu", + encrypted_len); + return NULL; + } + + plain_len = encrypted_len - 8; + decrypted = wpabuf_alloc(plain_len); + if (!decrypted) { + wpa_printf(MSG_INFO, + "NAN: Failed to allocate decryption buffer"); + return NULL; + } + + buf = wpabuf_put(decrypted, plain_len); + if (aes_unwrap(kek, kek_len, plain_len / 8, encrypted_data, buf)) { + wpa_printf(MSG_INFO, + "NAN: AES unwrap failed - could not decrypt key data"); + wpabuf_free(decrypted); + return NULL; + } + + wpa_hexdump_key(MSG_DEBUG, "NAN: Decrypted key data", + wpabuf_head(decrypted), wpabuf_len(decrypted)); + + return decrypted; +} + +/** + * Walk a sequence of NAN KDEs (Vendor Specific elements with WFA OUI) in the + * decrypted Key Data and copy the NIK and lifetime fields into output args. + */ +static int nan_pasn_parse_nik_kdes(const u8 *data, size_t len, int cipher, + u8 *nik, u8 *cipher_ver, + u32 *lifetime_sec, u16 *lifetime_bitmap) +{ + size_t gtk_key_len = nan_pasn_cipher_tk_len(cipher); + size_t gtk_kde_min = 2 + 6 + gtk_key_len; + /* RSN KDE selectors. Defined locally so the parser stays usable even when + * the upstream macros are gated by CONFIG_IEEE80211W or are absent (BIGTK). + */ + static const u32 sel_igtk = RSN_SELECTOR(0x00, 0x0f, 0xac, 9); + static const u32 sel_bigtk = RSN_SELECTOR(0x00, 0x0f, 0xac, 14); + static const u8 wfa_oui[3] = { 0x50, 0x6f, 0x9a }; + bool nik_found = false; + const u8 *pos = data; + const u8 *end = data + len; + + while (pos + 2 <= end) { + u8 id = pos[0]; + u8 elen = pos[1]; + + if (pos + 2 + elen > end) { + return -1; + } + if (id != WLAN_EID_VENDOR_SPECIFIC || elen < 4) { + pos += 2 + elen; + continue; + } + + const u32 selector = RSN_SELECTOR_GET(pos + 2); + const u8 oui_type = pos[5]; + const u8 *kde_body = pos + 6; + size_t kde_body_len = elen - 4; + + if (os_memcmp(pos + 2, wfa_oui, sizeof(wfa_oui)) == 0) { + if (oui_type == NAN_PASN_KDE_OUI_TYPE_NIK && + kde_body_len >= 1 + NAN_PASN_NIK_LEN) { + if (cipher_ver) { + *cipher_ver = kde_body[0]; + } + os_memcpy(nik, kde_body + 1, NAN_PASN_NIK_LEN); + nik_found = true; + wpa_printf(MSG_DEBUG, "NAN: NIK KDE cipher_ver=%u", + kde_body[0]); + wpa_hexdump_key(MSG_DEBUG, "NAN: NIK", + kde_body + 1, NAN_PASN_NIK_LEN); + } else if (oui_type == NAN_PASN_KDE_OUI_TYPE_LIFETIME && + kde_body_len >= 6) { + if (lifetime_bitmap) { + *lifetime_bitmap = WPA_GET_LE16(kde_body); + } + if (lifetime_sec) { + *lifetime_sec = WPA_GET_BE32(kde_body + 2); + } + } + } else if (gtk_key_len && selector == sel_igtk && + kde_body_len >= gtk_kde_min) { + /* IGTK KDE: KeyID(2 LE) | IPN(6) | IGTK */ + size_t igtk_len = kde_body_len - 8; + wpa_printf(MSG_DEBUG, + "NAN: IGTK KDE KeyID=%u igtk_len=%zu", + WPA_GET_LE16(kde_body), igtk_len); + wpa_hexdump(MSG_DEBUG, "NAN: IGTK IPN", kde_body + 2, 6); + wpa_hexdump_key(MSG_DEBUG, "NAN: IGTK", + kde_body + 8, igtk_len); + ESP_LOG_BUFFER_HEXDUMP("IGTK", kde_body + 8, igtk_len, ESP_LOG_INFO); + } else if (gtk_key_len && selector == sel_bigtk && + kde_body_len >= gtk_kde_min) { + /* BIGTK KDE: KeyID(2 LE) | BIPN(6) | BIGTK */ + size_t bigtk_len = kde_body_len - 8; + wpa_printf(MSG_DEBUG, + "NAN: BIGTK KDE KeyID=%u bigtk_len=%zu", + WPA_GET_LE16(kde_body), bigtk_len); + wpa_hexdump(MSG_DEBUG, "NAN: BIPN", kde_body + 2, 6); + wpa_hexdump_key(MSG_DEBUG, "NAN: BIGTK", + kde_body + 8, bigtk_len); + ESP_LOG_BUFFER_HEXDUMP("BIGTK", kde_body + 8, bigtk_len, ESP_LOG_INFO); + } + + pos += 2 + elen; + } + + /* Lifetime KDE is optional in practice (e.g. iPhone omits it), so only + * the NIK is required here. + */ + return nik_found ? 0 : -1; +} + +/** + * Expected format of shared_key_attr (NCS-PK-PASN-128, MIC=16): + * + * Offset Size Field Source / spec ref + * ───────────────────────────────────────────────────────────────────── + * NAN attribute header (Wi-Fi Aware v4.0, Table 125) + * 0x00 1 Attribute ID = 0x24 NAN_ATTR_SHARED_KEY_DESCR + * 0x01 2 Attribute Length (LE) body length, not incl. header + * 0x03 1 Publish ID struct nan_shared_key.publish_id + * + * IEEE 802.11 RSNA Key Descriptor (EAPOL-Key body, IEEE 802.11-2020 §12.7.2) + * 0x04 1 Descriptor Type = 0x02 NAN_KEY_DESC (Wi-Fi Aware fixed) + * 0x05 2 Key Information (BE) + * 0x07 2 Key Length (BE) = 0x0000 not carrying a pairwise cipher key + * 0x09 8 Key Replay Counter unused for PASN one-shot + * 0x11 32 Key Nonce unused (no 4-way handshake) + * 0x31 16 EAPOL-Key IV unused + * 0x41 8 Key RSC unused + * 0x49 8 Reserved (Key ID) + * 0x51 16 Key MIC HMAC over body w/ MIC zeroed + * 0x61 2 Key Data Length (BE) length of the wrapped blob + * 0x63 N Key Data AES-WRAP(KEK, KDEs || pad) + * + * Total = 4 + 95 + N bytes. + * + * NCS-PK-PASN-128 (CCMP) and NCS-PK-PASN-256 (GCMP-256) use cipher-dependent + * KEK/MIC lengths (see @c nan_pasn_cipher_*_len). + */ +int nan_pasn_followup_decrypt_keys(const uint8_t *shared_key_attr, + size_t attr_total_len, + uint8_t *nik, size_t nik_size, + uint8_t *cipher_ver, + uint32_t *lifetime_sec) +{ + const struct nan_pasn_key_material *saved; + const struct wpa_eapol_key *key_desc; + const u8 *body; + size_t body_len; + u16 attr_body_len; + u16 key_info; + u16 key_data_len; + u8 found_cipher_ver = 0; + u32 found_lifetime = 0; + u16 lifetime_bitmap = 0; + struct wpabuf *key_data = NULL; + size_t eapol_hdrlen; + size_t mic_len; + int ret = -1; + + if (!shared_key_attr || !nik || nik_size < NAN_PASN_NIK_LEN) { + return -1; + } + + /* Attribute header: ID(1) + Length(2 LE). */ + if (attr_total_len < 3 || shared_key_attr[0] != NAN_ATTR_SHARED_KEY_DESCR) { + wpa_printf(MSG_INFO, "NAN: Invalid Shared Key Descriptor attribute"); + return -1; + } + + attr_body_len = WPA_GET_LE16(&shared_key_attr[1]); + if ((size_t)attr_body_len + 3 > attr_total_len) { + wpa_printf(MSG_INFO, + "NAN: Truncated Shared Key Descriptor attribute (len=%u, total=%zu)", + attr_body_len, attr_total_len); + return -1; + } + + saved = nan_pasn_get_saved_keys(); + if (!saved || !saved->kek_len) { + wpa_printf(MSG_INFO, + "NAN: No saved KEK available to decrypt Shared Key Descriptor"); + return -1; + } + if (!nan_pasn_cipher_is_supported(saved->cipher)) { + wpa_printf(MSG_INFO, + "NAN: Unsupported cipher 0x%x for Shared Key Descriptor", + saved->cipher); + return -1; + } + + eapol_hdrlen = nan_pasn_cipher_eapol_key_hdrlen(saved->cipher); + mic_len = nan_pasn_cipher_mic_len(saved->cipher); + if (!eapol_hdrlen || !mic_len) { + return -1; + } + + /* + * Body layout (Wi-Fi Aware spec v4.0 + IEEE 802.11 EAPOL-Key): + * publish_id(1) + EAPOL-Key descriptor (MIC length per cipher) + + * key_data. + */ + if (attr_body_len < 1 + eapol_hdrlen) { + wpa_printf(MSG_INFO, + "NAN: Shared Key Descriptor body too short (%u, need %zu)", + attr_body_len, (size_t)(1 + eapol_hdrlen)); + return -1; + } + + body = &shared_key_attr[3]; + body_len = attr_body_len; + + /* Skip the 1-byte Publish ID; key descriptor starts at offset 1. */ + key_desc = (const struct wpa_eapol_key *)(body + 1); + key_info = WPA_GET_BE16(key_desc->key_info); + + if (!(key_info & WPA_KEY_INFO_KEY_TYPE)) { + wpa_printf(MSG_INFO, + "NAN: Follow-up frame does not contain pairwise key"); + return -1; + } + if (!(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) { + wpa_printf(MSG_INFO, + "NAN: Follow-up frame does not contain encrypted key data"); + return -1; + } + + key_data_len = WPA_GET_BE16(key_desc->key_data_length); + if ((size_t)1 + eapol_hdrlen + key_data_len > body_len) { + wpa_printf(MSG_INFO, + "NAN: Shared Key Descriptor key data overruns attribute (key_data_len=%u, body_len=%zu, eapol_hdrlen=%zu)", + key_data_len, body_len, eapol_hdrlen); + return -1; + } + + wpa_printf(MSG_DEBUG, + "NAN: Shared Key Descr cipher=%d mic_len=%zu key_data_len=%u body_len=%zu", + saved->cipher, mic_len, key_data_len, body_len); + + key_data = nan_crypto_decrypt_key_data(saved->kek, saved->kek_len, + body + 1 + eapol_hdrlen, + key_data_len); + if (!key_data) { + wpa_printf(MSG_INFO, + "NAN: Failed to decrypt Shared Key Descriptor key data"); + return -1; + } + + ESP_LOG_BUFFER_HEXDUMP("Key Data", wpabuf_head(key_data), wpabuf_len(key_data), ESP_LOG_INFO); + if (nan_pasn_parse_nik_kdes(wpabuf_head(key_data), wpabuf_len(key_data), + saved->cipher, nik, &found_cipher_ver, + &found_lifetime, &lifetime_bitmap) != 0) { + wpa_printf(MSG_INFO, + "NAN: NIK KDE missing in decrypted key data"); + goto out; + } + + /* Lifetime KDE is optional; if present, its bitmap must mark NIK. */ + if (found_lifetime && + !(lifetime_bitmap & NAN_PASN_KEY_LIFETIME_NIK_BIT)) { + wpa_printf(MSG_INFO, + "NAN: Unexpected key bitmap in Key Lifetime KDE: 0x%04x", + lifetime_bitmap); + goto out; + } + + if (cipher_ver) { + *cipher_ver = found_cipher_ver; + } + if (lifetime_sec) { + *lifetime_sec = found_lifetime; + } + + wpa_hexdump_key(MSG_DEBUG, "NAN: Peer NIK from follow-up", nik, + NAN_PASN_NIK_LEN); + ret = 0; + +out: + wpabuf_clear_free(key_data); + return ret; +} + static int nan_chan_to_freq_mhz(uint8_t chan) { if (chan >= 1 && chan <= 13) { @@ -520,10 +1020,15 @@ void nan_pasn_initialize(struct nan_pasn_data *nan, const u8 *addr, int freq, bo pasn->cipher = WPA_CIPHER_CCMP; pasn_enable_kdk_derivation(pasn); - if (!derive_kek) { - pasn->derive_kek = false; - pasn->kek_len = 0; - } + /* + * NAN PASN uses kek_len 0 for in-frame PASN-PTK; NAN Management KEK comes + * from KDK via nan_crypto_derive_kek in nan_pasn_copy_keys_from_pasn. + * ND-PMK is filled by pasn_nd_pmk_derive_from_kdk_store (hostap + * nan_crypto_derive_nd_pmk_from_kdk). Matches hostap nan_pairing.c. + */ + (void)derive_kek; + pasn->derive_kek = false; + pasn->kek_len = 0; if (nan->dev_sae_pin_len > 0) { pasn->akmp = WPA_KEY_MGMT_SAE; @@ -742,14 +1247,14 @@ static int nan_handle_pasn_auth(struct nan_pasn_data *nan, return -1; } nan_pasn_auth_timeout_cancel(nan); - nan_pasn_post_pasn_pairing_confirm_evt( - nan, pasn, le_to_host16(mgmt->auth.status_code), - mgmt->sa, nan->cfg->dev_addr, false); #ifdef CONFIG_TESTING_OPTIONS nan_pasn_store_ptk(nan, &pasn->ptk); #endif /* CONFIG_TESTING_OPTIONS */ nan_pasn_copy_keys_from_pasn(nan, pasn); - nan_pasn_install_nan_pairwise_tk(pasn); + if (nan_pasn_install_nan_pairwise_tk(nan, pasn) == 0 && + nan->pairing_key_installed_cb) { + nan->pairing_key_installed_cb(pasn->peer_addr); + } forced_memzero(pasn_get_ptk(pasn), sizeof(pasn->ptk)); nan_pasn_data_deinit(nan); } @@ -790,12 +1295,17 @@ int nan_pasn_auth_rx(struct nan_pasn_data *nan, const struct ieee80211_auth *mgm if (ret < 0) { wpa_printf(MSG_INFO, "PASN: wpa_pasn_auth_rx() failed"); nan->dev_role = NAN_ROLE_IDLE; - } else { - nan_pasn_post_pasn_pairing_confirm_evt( - nan, pasn, le_to_host16(mgmt->auth.status_code), - pasn->own_addr, pasn->peer_addr, true); + } else if (ret == 0 && pasn->status == WLAN_STATUS_SUCCESS) { + /* + * Pairing setup confirm maps to PASN completion. For initiator, + * this is only after Auth2 has been validated and Auth3 has been + * successfully built/transmitted by wpa_pasn_auth_rx(). + */ nan_pasn_copy_keys_from_pasn(nan, pasn); - nan_pasn_install_nan_pairwise_tk(pasn); + if (nan_pasn_install_nan_pairwise_tk(nan, pasn) == 0 && + nan->pairing_key_installed_cb) { + nan->pairing_key_installed_cb(pasn->peer_addr); + } } #ifdef CONFIG_TESTING_OPTIONS nan_pasn_store_ptk(nan, &pasn->ptk); @@ -1033,6 +1543,7 @@ int nan_pasn_auth_initiate(struct nan_pasn_data *pd, const uint8_t *peer_addr, i struct nan_pasn_eloop_ctx { uint8_t peer_addr[ETH_ALEN]; uint32_t pincode; + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb; }; static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data) @@ -1061,6 +1572,7 @@ static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data) } esp_nan_app_set_pasn_data(pd); + pd->pairing_key_installed_cb = ctx->pairing_key_installed_cb; if (ctx->pincode != UINT32_MAX) { n = os_snprintf(pin_digits, sizeof(pin_digits), "%06u", @@ -1090,21 +1602,19 @@ static void nan_pasn_auth_eloop_cb(void *eloop_ctx, void *user_data) os_free(ctx); } -int nan_pasn_auth_eloop(const uint8_t *peer_addr, uint32_t pincode) +int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb) { struct nan_pasn_eloop_ctx *ctx; - if (!peer_addr) { - return -1; - } - ctx = os_zalloc(sizeof(*ctx)); - if (!ctx) { + if (!ctx || !peer_nmi) { return -1; } - os_memcpy(ctx->peer_addr, peer_addr, ETH_ALEN); + os_memcpy(ctx->peer_addr, peer_nmi, ETH_ALEN); ctx->pincode = pincode; + ctx->pairing_key_installed_cb = pairing_key_installed_cb; if (eloop_register_timeout(0, 0, nan_pasn_auth_eloop_cb, NULL, ctx) != 0) { os_free(ctx); @@ -1255,36 +1765,41 @@ fail: struct pasn_responder_eloop_ctx { uint8_t peer_addr[ETH_ALEN]; - unsigned int has_peer; uint32_t pincode; + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb; }; static void pasn_responder_init_eloop_cb(void *eloop_ctx, void *user_data) { struct pasn_responder_eloop_ctx *ctx = user_data; + struct nan_pasn_data *pd; (void)eloop_ctx; if (!ctx) { return; } - pasn_responder_init(ctx->has_peer ? ctx->peer_addr : NULL, ctx->pincode); + if (pasn_responder_init(ctx->peer_addr, ctx->pincode) == 0) { + pd = esp_nan_app_get_pasn_data(); + if (pd) { + pd->pairing_key_installed_cb = ctx->pairing_key_installed_cb; + } + } os_free(ctx); } -int pasn_responder_init_eloop(const uint8_t *peer_addr, uint32_t pincode) +int esp_nan_supp_pasn_responder_init(const uint8_t *peer_nmi, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb) { struct pasn_responder_eloop_ctx *ctx; ctx = os_zalloc(sizeof(*ctx)); - if (!ctx) { + if (!ctx || !peer_nmi) { return -1; } ctx->pincode = pincode; - if (peer_addr) { - ctx->has_peer = 1; - os_memcpy(ctx->peer_addr, peer_addr, ETH_ALEN); - } + os_memcpy(ctx->peer_addr, peer_nmi, ETH_ALEN); + ctx->pairing_key_installed_cb = pairing_key_installed_cb; if (eloop_register_timeout(0, 0, pasn_responder_init_eloop_cb, NULL, ctx) != 0) { os_free(ctx); @@ -1389,6 +1904,24 @@ int pasn_responder_init_eloop(const uint8_t *peer_addr, uint32_t pincode) return -1; } +int esp_nan_supp_pasn_responder_init(const uint8_t *peer_nmi, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb) +{ + (void)peer_nmi; + (void)pincode; + (void)pairing_key_installed_cb; + return -1; +} + +int esp_nan_supp_pasn_initiator_auth(const uint8_t *peer_nmi, uint32_t pincode, + esp_nan_pairing_key_installed_cb_t pairing_key_installed_cb) +{ + (void)peer_nmi; + (void)pincode; + (void)pairing_key_installed_cb; + return -1; +} + const struct nan_pasn_key_material *nan_pasn_get_saved_keys(void) { return NULL; @@ -1398,4 +1931,19 @@ void nan_pasn_clear_saved_keys(void) { } +int nan_pasn_followup_decrypt_keys(const uint8_t *shared_key_attr, + size_t attr_total_len, + uint8_t *nik, size_t nik_size, + uint8_t *cipher_ver, + uint32_t *lifetime_sec) +{ + (void)shared_key_attr; + (void)attr_total_len; + (void)nik; + (void)nik_size; + (void)cipher_ver; + (void)lifetime_sec; + return -1; +} + #endif /* CONFIG_ESP_WIFI_PASN_SUPPORT */ diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h b/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h index 6811351cf55..93c7de167f8 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h @@ -234,6 +234,12 @@ enum key_flag { KEY_FLAG_PMK = BIT(6), }; +typedef enum { + NAN_KEY_ND_TK = 0, + NAN_KEY_ND_GTK, + NAN_KEY_NM_TK, +} nan_key_type_t; + typedef wifi_scan_channel_bitmap_t channel_bitmap_t; uint8_t *esp_wifi_ap_get_prof_pmk_internal(void); diff --git a/components/wpa_supplicant/src/common/wpa_common.c b/components/wpa_supplicant/src/common/wpa_common.c index e5b983d8284..2e72886327e 100644 --- a/components/wpa_supplicant/src/common/wpa_common.c +++ b/components/wpa_supplicant/src/common/wpa_common.c @@ -320,12 +320,9 @@ static int rsn_selector_to_bitfield(const u8 *s) return WPA_CIPHER_BIP_GMAC_256; #endif #endif /* CONFIG_IEEE80211W */ - if (RSN_SELECTOR_GET(s) == RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED){ - printf("### function = %s line = %di ###\n",__func__,__LINE__); + if (RSN_SELECTOR_GET(s) == RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED) return WPA_CIPHER_GTK_NOT_USED; - } - printf("### function = %s line = %di ###\n",__func__,__LINE__); return 0; } @@ -2079,7 +2076,6 @@ int wpa_pasn_validate_rsne(const struct wpa_ie_data *data) wpa_printf(MSG_DEBUG, "PASN: Invalid group data cipher"); return -1; } - printf("### function = %s line = %d %d %d ###\n",__func__,__LINE__,data->has_pairwise,data->pairwise_cipher); if (!data->has_pairwise || !data->pairwise_cipher || (data->pairwise_cipher & (data->pairwise_cipher - 1))) { @@ -2353,11 +2349,76 @@ void pasn_nd_pmk_global_clear(void) /** - * pasn_pmk_to_ptk - Calculate PASN PTK from PMK, addresses, etc. + * pasn_nd_pmk_derive_from_kdk_store - NAN ND-PMK from NM-KDK (hostap equivalent) + * + * Mirrors hostap nan_crypto_derive_nd_pmk_from_kdk() / nan_crypto_derive_from_kdk(): + * + * ND-PMK = KDF-HASH-NNN(KDK, "NDP PMK Derivation", + * Pairing Initiator NMI || Pairing Responder NMI) + * + * NNN follows the NAN PASN cipher suite: SHA-256 for 128-bit suite (e.g. CCMP), + * SHA-384 for 256-bit suite (GCMP-256). Output is always PMK_LEN octets. + */ +int pasn_nd_pmk_derive_from_kdk_store(const u8 *kdk, size_t kdk_len, + int pairwise_cipher, + const u8 *initiator_nmi, + const u8 *responder_nmi) +{ + static const char label[] = "NDP PMK Derivation"; + u8 data[2 * ETH_ALEN]; + int ret; + + if (!kdk || !kdk_len || !initiator_nmi || !responder_nmi) + return -1; + + os_memcpy(data, initiator_nmi, ETH_ALEN); + os_memcpy(data + ETH_ALEN, responder_nmi, ETH_ALEN); + + wpa_printf(MSG_DEBUG, "PASN: Deriving ND-PMK from NM-KDK (NAN pairing)"); + wpa_hexdump_key(MSG_DEBUG, "PASN: KDK", kdk, kdk_len); + wpa_printf(MSG_DEBUG, "PASN: Initiator NMI " MACSTR, + MAC2STR(initiator_nmi)); + wpa_printf(MSG_DEBUG, "PASN: Responder NMI " MACSTR, + MAC2STR(responder_nmi)); + + if (pairwise_cipher == WPA_CIPHER_GCMP_256) { +#ifdef CONFIG_SHA384 + wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA384"); + + ret = sha384_prf(kdk, kdk_len, label, data, sizeof(data), + pasn_nd_pmk_global.nd_pmk, PMK_LEN); +#else /* CONFIG_SHA384 */ + wpa_printf(MSG_DEBUG, "PASN: ND-PMK SHA384 not supported"); + return -1; +#endif /* CONFIG_SHA384 */ + } else { + wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA256"); + + ret = sha256_prf(kdk, kdk_len, label, data, sizeof(data), + pasn_nd_pmk_global.nd_pmk, PMK_LEN); + } + + if (ret < 0) + return -1; + + pasn_nd_pmk_global.valid = 1; + wpa_hexdump_key(MSG_DEBUG, "PASN: ND-PMK (global):", + pasn_nd_pmk_global.nd_pmk, PMK_LEN); + + return 0; +} + + +/** + * pasn_pmk_to_ptk - Calculate PASN/EPPKE PTK from PMK, addresses, etc. * @pmk: Pairwise master key * @pmk_len: Length of PMK - * @spa: Suppplicant address - * @bssid: AP BSSID + * @spa: For EPPKE authentication, non-AP MLD MAC address is used for MLO. For + * PASN authentication or EPPKE authentication for non-MLO, non-AP STA link + * MAC address is used. + * @bssid: For EPPKE authentication, AP MLD MAC address is used for MLO. For + * PASN authentication or EPPKE authentication for non-MLO, AP BSSID is + * used. * @dhss: Is the shared secret (DHss) derived from the PASN ephemeral key * exchange encoded as an octet string * @dhss_len: The length of dhss in octets @@ -2367,29 +2428,24 @@ void pasn_nd_pmk_global_clear(void) * @kdk_len: the length in octets that should be derived for HTLK. Can be zero. * @kek_len: The length in octets that should be derived for KEK. Can be zero. * @alg: Output variable for indicating the selected hash algorithm + * @is_eppke: EPPKE authentication * Returns: 0 on success, -1 on failure */ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, const u8 *spa, const u8 *bssid, const u8 *dhss, size_t dhss_len, struct wpa_ptk *ptk, int akmp, int cipher, - size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg) + size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg, + bool is_eppke) { u8 tmp[WPA_KCK_MAX_LEN + WPA_KEK_MAX_LEN + WPA_TK_MAX_LEN + WPA_KDK_MAX_LEN]; - u8 kek_buf[WPA_KEK_MAX_LEN]; - u8 nd_pmk_buf[PMK_LEN]; const u8 *pos; u8 *data; size_t data_len, ptk_len; - size_t first_prf_len; - const size_t nan_mgmt_kek_len = 16; int ret = -1; - const char *label = "PASN PTK Derivation"; - const char *kek_label = "NAN Management KEK Derivation"; - const char *nd_pmk_label = "NDP PMK Derivation"; - - (void) kek_len; + const char *label = is_eppke ? "EPPKE PTK Derivation" : + "PASN PTK Derivation"; if (!pmk || !pmk_len) { wpa_printf(MSG_ERROR, "PASN: No PMK set for PTK derivation"); @@ -2401,10 +2457,14 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, return -1; } - pasn_nd_pmk_global_clear(); - /* - * PASN-PTK = KDF(PMK, “PASN PTK Derivation”, SPA || BSSID || DHss) + * Use "EPPKE PTK Derivation" instead of "PASN PTK Derivation" for + * EPPKE Authentication per IEEE P802.11bi/D4.0, 12.16.9.3.4 (PTKSA + * derivation and MIC computation with EPPKE authentication). For EPPKE + * MLO, the non-AP MLD MAC address is used instead of the SPA and the + * AP MLD MAC address instead of the BSSID. + * + * PASN-PTK = KDF(PMK, "PASN PTK Derivation", SPA || BSSID || DHss) * * KCK = L(PASN-PTK, 0, 256) * TK = L(PASN-PTK, 256, TK_bits) @@ -2419,11 +2479,10 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, os_memcpy(data + ETH_ALEN, bssid, ETH_ALEN); os_memcpy(data + 2 * ETH_ALEN, dhss, dhss_len); - /* KEK is not taken from the first PASN-PTK layout; optional NAN KEK below. */ ptk->kck_len = WPA_PASN_KCK_LEN; ptk->tk_len = wpa_cipher_key_len(cipher); ptk->kdk_len = kdk_len; - ptk->kek_len = 0; + ptk->kek_len = kek_len; ptk->kek2_len = 0; ptk->kck2_len = 0; @@ -2434,14 +2493,16 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, goto err; } - first_prf_len = ptk->kck_len + ptk->tk_len + ptk->kdk_len; - if (first_prf_len > sizeof(tmp)) + ptk_len = ptk->kck_len + ptk->tk_len + ptk->kdk_len + ptk->kek_len; + if (ptk_len > sizeof(tmp)) goto err; - ptk_len = first_prf_len; *alg = pasn_select_hash_alg(akmp, cipher, pmk_len); switch (*alg) { + case RSN_HASH_SHA512: + wpa_printf(MSG_DEBUG, "PASN: SHA512 PTK derivation not supported"); + goto err; case RSN_HASH_SHA384: #ifdef CONFIG_SHA384 wpa_printf(MSG_DEBUG, "PASN: PTK derivation using SHA384"); @@ -2450,7 +2511,7 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, ptk_len) < 0) goto err; break; -#endif +#endif /* CONFIG_SHA384 */ case RSN_HASH_SHA256: wpa_printf(MSG_DEBUG, "PASN: PTK derivation using SHA256"); @@ -2476,6 +2537,13 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, wpa_hexdump_key(MSG_DEBUG, "PASN: KCK:", ptk->kck, WPA_PASN_KCK_LEN); pos = &tmp[WPA_PASN_KCK_LEN]; + if (kek_len) { + os_memcpy(ptk->kek, pos, kek_len); + wpa_hexdump_key(MSG_DEBUG, "PASN: KEK:", + ptk->kek, ptk->kek_len); + pos += kek_len; + } + os_memcpy(ptk->tk, pos, ptk->tk_len); wpa_hexdump_key(MSG_DEBUG, "PASN: TK:", ptk->tk, ptk->tk_len); pos += ptk->tk_len; @@ -2484,78 +2552,12 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, os_memcpy(ptk->kdk, pos, ptk->kdk_len); wpa_hexdump_key(MSG_DEBUG, "PASN: KDK:", ptk->kdk, ptk->kdk_len); - - /* - * NAN Management KEK = KDF(KDK, "NAN Management KEK Derivation", - * SPA || BSSID || DHss) - */ - switch (*alg) { - case RSN_HASH_SHA384: -#ifdef CONFIG_SHA384 - wpa_printf(MSG_DEBUG, "PASN: KEK derivation using SHA384"); - - if (sha384_prf(ptk->kdk, ptk->kdk_len, kek_label, data, - data_len, kek_buf, nan_mgmt_kek_len) < 0) - goto err; - break; -#endif - case RSN_HASH_SHA256: - wpa_printf(MSG_DEBUG, "PASN: KEK derivation using SHA256"); - - if (sha256_prf(ptk->kdk, ptk->kdk_len, kek_label, data, - data_len, kek_buf, nan_mgmt_kek_len) < 0) - goto err; - break; - default: - wpa_printf(MSG_DEBUG, "PASN: Unsupported hash algorithm %d", - *alg); - goto err; - } - - os_memcpy(ptk->kek, kek_buf, nan_mgmt_kek_len); - ptk->kek_len = nan_mgmt_kek_len; - wpa_hexdump_key(MSG_DEBUG, "PASN: KEK (NAN management):", - ptk->kek, ptk->kek_len); - - /* - * ND-PMK = KDF(KDK, "NDP PMK Derivation", SPA || BSSID || DHss) - */ - switch (*alg) { - case RSN_HASH_SHA384: -#ifdef CONFIG_SHA384 - wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA384"); - - if (sha384_prf(ptk->kdk, ptk->kdk_len, nd_pmk_label, data, - data_len, nd_pmk_buf, PMK_LEN) < 0) - goto err; - break; -#endif - case RSN_HASH_SHA256: - wpa_printf(MSG_DEBUG, "PASN: ND-PMK derivation using SHA256"); - - if (sha256_prf(ptk->kdk, ptk->kdk_len, nd_pmk_label, data, - data_len, nd_pmk_buf, PMK_LEN) < 0) - goto err; - break; - default: - wpa_printf(MSG_DEBUG, "PASN: Unsupported hash algorithm %d", - *alg); - goto err; - } - - os_memcpy(pasn_nd_pmk_global.nd_pmk, nd_pmk_buf, PMK_LEN); - pasn_nd_pmk_global.valid = 1; - wpa_hexdump_key(MSG_DEBUG, "PASN: ND-PMK (global):", - pasn_nd_pmk_global.nd_pmk, PMK_LEN); - forced_memzero(nd_pmk_buf, sizeof(nd_pmk_buf)); } - ptk->ptk_len = ptk->kck_len + ptk->kek_len + ptk->tk_len + ptk->kdk_len; + ptk->ptk_len = ptk_len; forced_memzero(tmp, sizeof(tmp)); ret = 0; err: - forced_memzero(kek_buf, sizeof(kek_buf)); - forced_memzero(nd_pmk_buf, sizeof(nd_pmk_buf)); bin_clear_free(data, data_len); return ret; } @@ -2568,6 +2570,8 @@ err: size_t pasn_mic_len(enum rsn_hash_alg alg) { switch (alg) { + case RSN_HASH_SHA512: + return 32; case RSN_HASH_SHA384: return 24; case RSN_HASH_SHA256: diff --git a/components/wpa_supplicant/src/common/wpa_common.h b/components/wpa_supplicant/src/common/wpa_common.h index aafb0128efa..e5b2d345165 100644 --- a/components/wpa_supplicant/src/common/wpa_common.h +++ b/components/wpa_supplicant/src/common/wpa_common.h @@ -28,7 +28,9 @@ #define WPA_PASN_MAX_MIC_LEN 32 /** - * NDP PMK (32 octets) from KDK in pasn_pmk_to_ptk (label "NDP PMK Derivation"). + * ND-PMK (32 octets) for Wi-Fi NAN pairing, derived from NM-KDK via + * pasn_nd_pmk_derive_from_kdk_store() (same KDF as hostap + * nan_crypto_derive_nd_pmk_from_kdk). * @valid: nonzero after successful derivation in the current session. */ struct pasn_nd_pmk_store { @@ -40,6 +42,11 @@ extern struct pasn_nd_pmk_store pasn_nd_pmk_global; void pasn_nd_pmk_global_clear(void); +int pasn_nd_pmk_derive_from_kdk_store(const u8 *kdk, size_t kdk_len, + int pairwise_cipher, + const u8 *initiator_nmi, + const u8 *responder_nmi); + #define COMEBACK_PENDING_IDX_SIZE 256 enum rsn_hash_alg { @@ -598,7 +605,8 @@ int pasn_pmk_to_ptk(const u8 *pmk, size_t pmk_len, const u8 *spa, const u8 *bssid, const u8 *dhss, size_t dhss_len, struct wpa_ptk *ptk, int akmp, int cipher, - size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg); + size_t kdk_len, size_t kek_len, enum rsn_hash_alg *alg, + bool is_eppke); size_t pasn_mic_len(enum rsn_hash_alg alg); diff --git a/components/wpa_supplicant/src/pasn/pasn_initiator.c b/components/wpa_supplicant/src/pasn/pasn_initiator.c index c712ff636ac..c75983c1ae5 100644 --- a/components/wpa_supplicant/src/pasn/pasn_initiator.c +++ b/components/wpa_supplicant/src/pasn/pasn_initiator.c @@ -1364,16 +1364,29 @@ int wpa_pasn_auth_rx(struct pasn_data *pasn, const u8 *data, size_t len, goto fail; } + pasn_nd_pmk_global_clear(); + ret = pasn_pmk_to_ptk(pasn->pmk, pasn->pmk_len, pasn->own_addr, pasn->peer_addr, wpabuf_head(secret), wpabuf_len(secret), &pasn->ptk, pasn->akmp, pasn->cipher, - pasn->kdk_len, pasn->kek_len, &pasn->hash_alg); + pasn->kdk_len, pasn->kek_len, &pasn->hash_alg, + false); if (ret) { wpa_printf(MSG_DEBUG, "PASN: Failed to derive PTK"); goto fail; } + if (pasn->ptk.kdk_len) { + ret = pasn_nd_pmk_derive_from_kdk_store( + pasn->ptk.kdk, pasn->ptk.kdk_len, pasn->cipher, + pasn->own_addr, pasn->peer_addr); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: Failed to derive ND-PMK"); + goto fail; + } + } + if (pasn->secure_ltf) { ret = wpa_ltf_keyseed(&pasn->ptk, pasn->akmp, pasn->cipher); if (ret) { diff --git a/components/wpa_supplicant/src/pasn/pasn_responder.c b/components/wpa_supplicant/src/pasn/pasn_responder.c index 97e5491dc16..16027977ef4 100644 --- a/components/wpa_supplicant/src/pasn/pasn_responder.c +++ b/components/wpa_supplicant/src/pasn/pasn_responder.c @@ -413,16 +413,28 @@ pasn_derive_keys(struct pasn_data *pasn, pasn->pmk_len = pmk_len; os_memcpy(pasn->pmk, pmk, pmk_len); + pasn_nd_pmk_global_clear(); + ret = pasn_pmk_to_ptk(pmk, pmk_len, peer_addr, own_addr, wpabuf_head(secret), wpabuf_len(secret), &pasn->ptk, pasn->akmp, pasn->cipher, pasn->kdk_len, pasn->kek_len, - &pasn->hash_alg); + &pasn->hash_alg, false); if (ret) { wpa_printf(MSG_DEBUG, "PASN: Failed to derive PTK"); return -1; } + if (pasn->ptk.kdk_len) { + ret = pasn_nd_pmk_derive_from_kdk_store( + pasn->ptk.kdk, pasn->ptk.kdk_len, pasn->cipher, + peer_addr, own_addr); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: Failed to derive ND-PMK"); + return -1; + } + } + if (pasn->secure_ltf) { ret = wpa_ltf_keyseed(&pasn->ptk, pasn->akmp, pasn->cipher);