diff --git a/components/esp_wifi/Kconfig b/components/esp_wifi/Kconfig index 46efd65416b..c8803d747f5 100644 --- a/components/esp_wifi/Kconfig +++ b/components/esp_wifi/Kconfig @@ -771,6 +771,17 @@ menu "Wi-Fi" help Select this option to enable WiFi Easy Connect Support. + config ESP_WIFI_DPP_MAX_CONF_OBJ + int "Maximum number of DPP configuration objects" + depends on ESP_WIFI_DPP_SUPPORT + range 1 10 + default 3 + help + Maximum number of configuration objects that can be received and stored + during a DPP configuration exchange. DPP v2 configurators may send + multiple configuration objects (e.g., for different SSIDs or AKMs). + Increasing this value will increase the memory usage for DPP events. + config ESP_WIFI_11R_SUPPORT bool "Enable 802.11R (Fast Transition) Support" default n diff --git a/components/esp_wifi/include/esp_wifi_types_generic.h b/components/esp_wifi/include/esp_wifi_types_generic.h index 709d55e4e4b..ac5c111f566 100644 --- a/components/esp_wifi/include/esp_wifi_types_generic.h +++ b/components/esp_wifi/include/esp_wifi_types_generic.h @@ -47,11 +47,13 @@ typedef enum { typedef enum { WIFI_OFFCHAN_TX_CANCEL, /**< Cancel off-channel transmission */ WIFI_OFFCHAN_TX_REQ, /**< Request off-channel transmission */ + WIFI_OFFCHAN_TX_CONNECTING_REQ, /**< Off-channel Tx request during connecting state; not recommended for use by public APIs */ } wifi_action_tx_t; typedef enum { WIFI_ROC_CANCEL, /**< Cancel remain on channel */ WIFI_ROC_REQ, /**< Request remain on channel */ + WIFI_ROC_CONNECTING_REQ, /**< Remain-on-channel request during connecting state; not recommended for use by public APIs */ } wifi_roc_t; /** * @brief Wi-Fi country policy @@ -1300,7 +1302,7 @@ typedef enum { WIFI_EVENT_STA_BEACON_OFFSET_UNSTABLE, /**< Station sampled beacon offset unstable */ WIFI_EVENT_DPP_URI_READY, /**< DPP URI is ready through Bootstrapping */ - WIFI_EVENT_DPP_CFG_RECVD, /**< Config received via DPP Authentication */ + WIFI_EVENT_DPP_CFG_RECVD, /**< DPP Configuration Response; payload is wifi_event_dpp_config_received_t */ WIFI_EVENT_DPP_FAILED, /**< DPP failed */ WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, /**< NAN Pairing Bootstrapping completed (success/failure) */ @@ -1756,14 +1758,75 @@ typedef struct { char uri[]; /**< URI data */ } wifi_event_dpp_uri_ready_t; -/** Argument structure for WIFI_EVENT_DPP_CFG_RECVD event */ +#define ESP_DPP_MAX_CONNECTOR_LEN 512 +#define ESP_DPP_MAX_KEY_LEN 128 + +/** + * @brief One provisioned network from a DPP Configuration Response + * + * Filled by the stack for WIFI_EVENT_DPP_CFG_RECVD. A single network row may contain hybrid + * credentials (e.g., both a DPP Connector and a legacy PSK/SAE password). The application + * should pass this structure to esp_supp_dpp_set_config() to load any DPP credentials, and + * also extract the SSID/password for esp_wifi_set_config() to configure the driver for + * legacy connection and fallback. + * + * @note Memory footprint: this structure uses fixed-size buffers for the connector and keys + * (dominated by ESP_DPP_MAX_CONNECTOR_LEN and ESP_DPP_MAX_KEY_LEN). While this avoids + * dynamic allocation and keeps the structure application-friendly, it results in a + * size of nearly 1KB per entry. + */ typedef struct { - wifi_config_t wifi_cfg; /**< Received WIFI config in DPP */ + uint8_t ssid[MAX_SSID_LEN]; /**< SSID octets; valid length is @ref ssid_len */ + uint8_t ssid_len; /**< SSID length in octets, 0 .. MAX_SSID_LEN */ + uint8_t password[MAX_PASSPHRASE_LEN]; /**< Legacy AKM: passphrase or hex PSK octets; length @ref password_len */ + uint8_t password_len; /**< Password length in octets, 0 .. MAX_PASSPHRASE_LEN; 0 if unused */ + char connector[ESP_DPP_MAX_CONNECTOR_LEN]; /**< DPP Connector when akm includes DPP; NUL-terminated when @ref connector_len > 0 */ + uint16_t connector_len; /**< Connector length in octets, excluding NUL; 0 if absent */ + uint8_t net_access_key[ESP_DPP_MAX_KEY_LEN]; /**< Network access key when akm includes DPP */ + uint16_t net_access_key_len; /**< Used length of net_access_key */ + uint8_t c_sign_key[ESP_DPP_MAX_KEY_LEN]; /**< C-sign key when akm includes DPP */ + uint16_t c_sign_key_len; /**< Used length of c_sign_key */ + uint64_t net_access_key_expiry; /**< Optional expiry hint for net_access_key from configurator */ + uint8_t curr_chan; /**< Channel from the DPP exchange for this row; used for off-channel network introduction */ + uint8_t akm; /**< AKM for this row; values are esp_dpp_akm_t (stored as uint8_t) */ +} esp_dpp_config_data_t; + +/** + * @brief Argument structure for WIFI_EVENT_DPP_CFG_RECVD event + * + * Contains STA configurations received from the DPP Configurator. wifi_cfg is + * populated from the first configuration object for backward compatibility. + * Application should iterate through configs[] if the first connection attempt fails. + * The event data is only valid within the callback handler; applications must + * copy the configurations if they need to use them after the handler returns. + * If the first configuration object from the Configurator uses a DPP AKM and includes a connector, the supplicant + * automatically loads it into the internal DPP store for Network Introduction for backward + * compatibility. esp_supp_dpp_set_config() replaces that store when the application selects a + * row explicitly. + * + * Only the first CONFIG_ESP_WIFI_DPP_MAX_CONF_OBJ objects from the Configurator are forwarded; additional objects in the + * DPP response are ignored. + * + * Variable-length payload: base struct plus total_conf elements of configs[]. + * + * @note Memory footprint: total event payload is roughly + * sizeof(wifi_event_dpp_config_received_t) + total_conf * sizeof(esp_dpp_config_data_t), + * i.e. ~1KB per forwarded configuration object. esp_event_post() deep-copies + * this payload into the event queue, so transient peak heap usage during the post is + * roughly twice the payload. Applications must copy these configurations if they + * need to use them after the event handler returns, as they will no longer be available. + * Memory-constrained applications can cap the payload by lowering + * CONFIG_ESP_WIFI_DPP_MAX_CONF_OBJ (default 3). + */ +typedef struct { + wifi_config_t wifi_cfg; /**< STA summary from the first configuration object */ + uint8_t total_conf; /**< Number of esp_dpp_config_data_t entries in configs */ + esp_dpp_config_data_t configs[]; /**< One row per provisioned network, configurator order */ } wifi_event_dpp_config_received_t; -/** Argument structure for WIFI_EVENT_DPP_FAIL event */ +/** Argument structure for WIFI_EVENT_DPP_FAILED event */ typedef struct { - int failure_reason; /**< Failure reason */ + int failure_reason; /**< esp_err_t code (e.g. ESP_ERR_DPP_FAILURE) */ } wifi_event_dpp_failed_t; #ifdef __cplusplus diff --git a/components/esp_wifi/lib b/components/esp_wifi/lib index 55636e0b8b4..e8218e5e8e2 160000 --- a/components/esp_wifi/lib +++ b/components/esp_wifi/lib @@ -1 +1 @@ -Subproject commit 55636e0b8b47f456d8e43ed8d027ccd1fa2e7af6 +Subproject commit e8218e5e8e2717e1d6fd6cc612b3fc7bed4183c9 diff --git a/components/esp_wifi/remote/Kconfig.wifi.in b/components/esp_wifi/remote/Kconfig.wifi.in index 7a346c28072..d7bbe9cbac0 100644 --- a/components/esp_wifi/remote/Kconfig.wifi.in +++ b/components/esp_wifi/remote/Kconfig.wifi.in @@ -750,6 +750,17 @@ config WIFI_RMT_DPP_SUPPORT help Select this option to enable WiFi Easy Connect Support. +config WIFI_RMT_DPP_MAX_CONF_OBJ + int "Maximum number of DPP configuration objects" + depends on WIFI_RMT_DPP_SUPPORT + range 1 10 + default 3 + help + Maximum number of configuration objects that can be received and stored + during a DPP configuration exchange. DPP v2 configurators may send + multiple configuration objects (e.g., for different SSIDs or AKMs). + Increasing this value will increase the memory usage for DPP events. + config WIFI_RMT_11R_SUPPORT bool "Enable 802.11R (Fast Transition) Support" default n diff --git a/components/esp_wifi/remote/Kconfig.wifi_is_remote.in b/components/esp_wifi/remote/Kconfig.wifi_is_remote.in index 776101d1cd9..8a1a4a5f3f3 100644 --- a/components/esp_wifi/remote/Kconfig.wifi_is_remote.in +++ b/components/esp_wifi/remote/Kconfig.wifi_is_remote.in @@ -396,6 +396,11 @@ if WIFI_RMT_DPP_SUPPORT default WIFI_RMT_DPP_SUPPORT endif +config ESP_WIFI_DPP_MAX_CONF_OBJ # ignore: multiple-definition + int + depends on WIFI_RMT_DPP_SUPPORT + default WIFI_RMT_DPP_MAX_CONF_OBJ + if WIFI_RMT_11R_SUPPORT config ESP_WIFI_11R_SUPPORT # ignore: multiple-definition bool 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 45eea50f89f..6debdfbecb3 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 @@ -47,11 +47,13 @@ typedef enum { typedef enum { WIFI_OFFCHAN_TX_CANCEL, /**< Cancel off-channel transmission */ WIFI_OFFCHAN_TX_REQ, /**< Request off-channel transmission */ + WIFI_OFFCHAN_TX_CONNECTING_REQ, /**< Off-channel Tx request during connecting state; not recommended for use by public APIs */ } wifi_action_tx_t; typedef enum { WIFI_ROC_CANCEL, /**< Cancel remain on channel */ WIFI_ROC_REQ, /**< Request remain on channel */ + WIFI_ROC_CONNECTING_REQ, /**< Remain-on-channel request during connecting state; not recommended for use by public APIs */ } wifi_roc_t; /** * @brief Wi-Fi country policy @@ -1300,7 +1302,7 @@ typedef enum { WIFI_EVENT_STA_BEACON_OFFSET_UNSTABLE, /**< Station sampled beacon offset unstable */ WIFI_EVENT_DPP_URI_READY, /**< DPP URI is ready through Bootstrapping */ - WIFI_EVENT_DPP_CFG_RECVD, /**< Config received via DPP Authentication */ + WIFI_EVENT_DPP_CFG_RECVD, /**< DPP Configuration Response; payload is wifi_event_dpp_config_received_t */ WIFI_EVENT_DPP_FAILED, /**< DPP failed */ WIFI_EVENT_NAN_BOOTSTRAP_INDICATION, /**< Received NAN Pairing Bootstrapping Request from a Peer */ WIFI_EVENT_NAN_BOOTSTRAP_COMPLETED, /**< NAN Pairing Bootstrapping completed (success/failure) */ @@ -1756,14 +1758,75 @@ typedef struct { char uri[]; /**< URI data */ } wifi_event_dpp_uri_ready_t; -/** Argument structure for WIFI_EVENT_DPP_CFG_RECVD event */ +#define ESP_DPP_MAX_CONNECTOR_LEN 512 +#define ESP_DPP_MAX_KEY_LEN 128 + +/** + * @brief One provisioned network from a DPP Configuration Response + * + * Filled by the stack for WIFI_EVENT_DPP_CFG_RECVD. A single network row may contain hybrid + * credentials (e.g., both a DPP Connector and a legacy PSK/SAE password). The application + * should pass this structure to esp_supp_dpp_set_config() to load any DPP credentials, and + * also extract the SSID/password for esp_wifi_set_config() to configure the driver for + * legacy connection and fallback. + * + * @note Memory footprint: this structure uses fixed-size buffers for the connector and keys + * (dominated by ESP_DPP_MAX_CONNECTOR_LEN and ESP_DPP_MAX_KEY_LEN). While this avoids + * dynamic allocation and keeps the structure application-friendly, it results in a + * size of nearly 1KB per entry. + */ typedef struct { - wifi_config_t wifi_cfg; /**< Received WIFI config in DPP */ + uint8_t ssid[MAX_SSID_LEN]; /**< SSID octets; valid length is @ref ssid_len */ + uint8_t ssid_len; /**< SSID length in octets, 0 .. MAX_SSID_LEN */ + uint8_t password[MAX_PASSPHRASE_LEN]; /**< Legacy AKM: passphrase or hex PSK octets; length @ref password_len */ + uint8_t password_len; /**< Password length in octets, 0 .. MAX_PASSPHRASE_LEN; 0 if unused */ + char connector[ESP_DPP_MAX_CONNECTOR_LEN]; /**< DPP Connector when akm includes DPP; NUL-terminated when @ref connector_len > 0 */ + uint16_t connector_len; /**< Connector length in octets, excluding NUL; 0 if absent */ + uint8_t net_access_key[ESP_DPP_MAX_KEY_LEN]; /**< Network access key when akm includes DPP */ + uint16_t net_access_key_len; /**< Used length of net_access_key */ + uint8_t c_sign_key[ESP_DPP_MAX_KEY_LEN]; /**< C-sign key when akm includes DPP */ + uint16_t c_sign_key_len; /**< Used length of c_sign_key */ + uint64_t net_access_key_expiry; /**< Optional expiry hint for net_access_key from configurator */ + uint8_t curr_chan; /**< Channel from the DPP exchange for this row; used for off-channel network introduction */ + uint8_t akm; /**< AKM for this row; values are esp_dpp_akm_t (stored as uint8_t) */ +} esp_dpp_config_data_t; + +/** + * @brief Argument structure for WIFI_EVENT_DPP_CFG_RECVD event + * + * Contains STA configurations received from the DPP Configurator. wifi_cfg is + * populated from the first configuration object for backward compatibility. + * Application should iterate through configs[] if the first connection attempt fails. + * The event data is only valid within the callback handler; applications must + * copy the configurations if they need to use them after the handler returns. + * If the first configuration object from the Configurator uses a DPP AKM and includes a connector, the supplicant + * automatically loads it into the internal DPP store for Network Introduction for backward + * compatibility. esp_supp_dpp_set_config() replaces that store when the application selects a + * row explicitly. + * + * Only the first CONFIG_WIFI_RMT_DPP_MAX_CONF_OBJ objects from the Configurator are forwarded; additional objects in the + * DPP response are ignored. + * + * Variable-length payload: base struct plus total_conf elements of configs[]. + * + * @note Memory footprint: total event payload is roughly + * sizeof(wifi_event_dpp_config_received_t) + total_conf * sizeof(esp_dpp_config_data_t), + * i.e. ~1KB per forwarded configuration object. esp_event_post() deep-copies + * this payload into the event queue, so transient peak heap usage during the post is + * roughly twice the payload. Applications must copy these configurations if they + * need to use them after the event handler returns, as they will no longer be available. + * Memory-constrained applications can cap the payload by lowering + * CONFIG_WIFI_RMT_DPP_MAX_CONF_OBJ (default 3). + */ +typedef struct { + wifi_config_t wifi_cfg; /**< STA summary from the first configuration object */ + uint8_t total_conf; /**< Number of esp_dpp_config_data_t entries in configs */ + esp_dpp_config_data_t configs[]; /**< One row per provisioned network, configurator order */ } wifi_event_dpp_config_received_t; -/** Argument structure for WIFI_EVENT_DPP_FAIL event */ +/** Argument structure for WIFI_EVENT_DPP_FAILED event */ typedef struct { - int failure_reason; /**< Failure reason */ + int failure_reason; /**< esp_err_t code (e.g. ESP_ERR_DPP_FAILURE) */ } wifi_event_dpp_failed_t; #ifdef __cplusplus diff --git a/components/wpa_supplicant/esp_supplicant/include/esp_dpp.h b/components/wpa_supplicant/esp_supplicant/include/esp_dpp.h index 0dacc6e421a..2c583efd0c2 100644 --- a/components/wpa_supplicant/esp_supplicant/include/esp_dpp.h +++ b/components/wpa_supplicant/esp_supplicant/include/esp_dpp.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +18,14 @@ extern "C" { #define ESP_DPP_MAX_CHAN_COUNT 5 +#ifndef ESP_DPP_MAX_CONFIG_COUNT +#ifdef CONFIG_ESP_WIFI_DPP_MAX_CONF_OBJ +#define ESP_DPP_MAX_CONFIG_COUNT CONFIG_ESP_WIFI_DPP_MAX_CONF_OBJ +#else +#define ESP_DPP_MAX_CONFIG_COUNT 3 +#endif +#endif + #define ESP_ERR_DPP_FAILURE (ESP_ERR_WIFI_BASE + 151) /*!< Generic failure during DPP Operation */ #define ESP_ERR_DPP_TX_FAILURE (ESP_ERR_WIFI_BASE + 152) /*!< DPP Frame Tx failed OR not Acked */ #define ESP_ERR_DPP_INVALID_ATTR (ESP_ERR_WIFI_BASE + 153) /*!< Encountered invalid DPP Attribute */ @@ -25,6 +33,22 @@ extern "C" { #define ESP_ERR_DPP_INVALID_LIST (ESP_ERR_WIFI_BASE + 155) /*!< Channel list given in esp_supp_dpp_bootstrap_gen() is not valid or too big */ #define ESP_ERR_DPP_CONF_TIMEOUT (ESP_ERR_WIFI_BASE + 156) /*!< DPP Configuration was not received in time */ +/** + * @brief AKM values for one DPP configuration row (which credentials apply and how to connect). + * + * Use this with fields in esp_dpp_config_data_t: legacy modes use password, + * DPP modes use connector and network access key via esp_supp_dpp_set_config(). + */ +typedef enum { + ESP_DPP_AKM_UNKNOWN = 0, /**< Not set or unrecognized */ + ESP_DPP_AKM_DPP = 1, /**< DPP-only: connector and network access key */ + ESP_DPP_AKM_PSK = 2, /**< WPA2-PSK: passphrase in password */ + ESP_DPP_AKM_SAE = 3, /**< WPA3-SAE: passphrase in password */ + ESP_DPP_AKM_PSK_SAE = 4, /**< WPA2/WPA3 transition: passphrase in password */ + ESP_DPP_AKM_SAE_DPP = 5, /**< SAE plus DPP: passphrase and DPP credentials */ + ESP_DPP_AKM_PSK_SAE_DPP = 6, /**< WPA2/WPA3 plus DPP: passphrase and DPP credentials */ +} esp_dpp_akm_t; + /** @brief Types of Bootstrap Methods for DPP. */ typedef enum dpp_bootstrap_type { DPP_BOOTSTRAP_QR_CODE, /**< QR Code Method */ @@ -37,10 +61,9 @@ typedef enum dpp_bootstrap_type { * * Starts DPP Supplicant and initializes related Data Structures. * - * @return + * return * - ESP_OK: Success - * - ESP_ERR_DPP_FAILURE: Generic failure or already initialized - * - ESP_ERR_NO_MEM: Memory allocation failed + * - ESP_FAIL: Failure */ esp_err_t esp_supp_dpp_init(void); @@ -51,7 +74,6 @@ esp_err_t esp_supp_dpp_init(void); * * @return * - ESP_OK: Success - * - ESP_ERR_DPP_FAILURE: Failed to schedule deinitialization */ esp_err_t esp_supp_dpp_deinit(void); @@ -61,20 +83,16 @@ esp_err_t esp_supp_dpp_deinit(void); * Generates Out Of Band Bootstrap information as an Enrollee which can be * used by a DPP Configurator to provision the Enrollee. * - * @param chan_list List of channels device will be available on for listening + * @param chan_list List of channels device will be available on for listening (must not be NULL) * @param type Bootstrap method type, only QR Code method is supported for now. - * @param key (Optional) 32 byte hex-encoded Private Key for generating a Bootstrapping Public Key + * @param key (Optional) 32 byte Raw Private Key for generating a Bootstrapping Public Key * @param info (Optional) Ancillary Device Information like Serial Number * - * @note Since this API is asynchronous, internal failures during generation (e.g. OOM or crypto errors) - * will be reported via the WIFI_EVENT_DPP_FAILED event. - * * @return - * - ESP_OK: Success (generation started asynchronously) - * - ESP_ERR_INVALID_STATE: DPP not initialized or shutting down - * - ESP_ERR_DPP_INVALID_LIST: Channel list not valid or too long - * - ESP_ERR_NO_MEM: Memory allocation failed - * - ESP_ERR_NOT_SUPPORTED: Requested bootstrap type not supported + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: chan_list is NULL + * - ESP_ERR_DPP_INVALID_LIST: Channel list not valid + * - ESP_FAIL: Failure */ esp_err_t esp_supp_dpp_bootstrap_gen(const char *chan_list, esp_supp_dpp_bootstrap_t type, @@ -87,8 +105,9 @@ esp_supp_dpp_bootstrap_gen(const char *chan_list, esp_supp_dpp_bootstrap_t type, * * @return * - ESP_OK: Success - * - ESP_ERR_INVALID_STATE: ROC attempted before WiFi is started, or DPP not initialized/bootstrapped - * - ESP_ERR_NO_MEM: Memory allocation failed while scheduling listen operation + * - ESP_FAIL: Generic Failure + * - ESP_ERR_INVALID_STATE: ROC attempted before WiFi is started + * - ESP_ERR_NO_MEM: Memory allocation failed while posting ROC request */ esp_err_t esp_supp_dpp_start_listen(void); @@ -99,10 +118,34 @@ esp_err_t esp_supp_dpp_start_listen(void); * * @return * - ESP_OK: Success - * - ESP_FAIL: Failure to schedule listen stop + * - ESP_FAIL: Failure */ esp_err_t esp_supp_dpp_stop_listen(void); +/** + * @brief Install or clear DPP AKM connector material in the supplicant. + * + * This function copies the given DPP AKM row (connector and related keys) into the + * supplicant; only one such row is retained, and a new row replaces the previous one. + * Pass NULL to clear the stored row. (If the first configuration object in the + * Configuration Response uses a DPP AKM with a connector, the stack may load it into the + * same store before the event is delivered; NULL clears that selection too.) + * + * Connection retry/fallback policy is application-owned. Typical sequence per selected + * row is: pick one row from WIFI_EVENT_DPP_CFG_RECVD, call esp_wifi_set_config(), + * call esp_supp_dpp_set_config() using connector values received from + * WIFI_EVENT_DPP_CFG_RECVD, call esp_wifi_connect(). + * + * @param config Pointer to one DPP AKM row from WIFI_EVENT_DPP_CFG_RECVD, or NULL to clear. + * + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: @a config is not a DPP AKM row, or key lengths are invalid + * - ESP_ERR_INVALID_STATE: DPP supplicant not initialized + * - ESP_ERR_NO_MEM: Allocation failed while storing the row + */ +esp_err_t esp_supp_dpp_set_config(const esp_dpp_config_data_t *config); + #ifdef __cplusplus } #endif 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 d3bbdd625b4..b867040c8d1 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 @@ -1683,8 +1683,13 @@ struct crypto_ec_key * crypto_ec_key_set_pub(const struct crypto_ec_group *group key_bits = bits; } - psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_VERIFY_HASH | PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_DERIVE); - psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + if (ecc_family == PSA_ECC_FAMILY_MONTGOMERY) { + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDH); + } else { + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_VERIFY_HASH | PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + } psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_PUBLIC_KEY(ecc_family)); psa_set_key_bits(&key_attributes, key_bits); diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_common.c b/components/wpa_supplicant/esp_supplicant/src/esp_common.c index 2c5160ffa85..c16ce8e7539 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_common.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_common.c @@ -18,7 +18,7 @@ #include "esp_scan_i.h" #include "esp_common_i.h" #include "common/ieee802_11_common.h" -#include "esp_dpp.h" +#include "esp_dpp_i.h" #include "esp_rrm.h" #include "esp_wnm.h" #include "rsn_supp/wpa_i.h" diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_common_i.h b/components/wpa_supplicant/esp_supplicant/src/esp_common_i.h index 13e886399c1..699d2f8d59d 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_common_i.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_common_i.h @@ -28,9 +28,6 @@ bool mbo_bss_profile_match(u8 *bssid); #endif /* defined(CONFIG_RRM) || defined(CONFIG_WNM) */ int esp_supplicant_common_init(struct wpa_funcs *wpa_cb); void esp_supplicant_common_deinit(void); -#ifdef CONFIG_DPP -esp_err_t esp_supp_dpp_common_init(void); -#endif void esp_supplicant_unset_all_appie(void); void esp_set_scan_ie(void); void esp_set_assoc_ie(uint8_t *bssid, const u8 *ies, size_t ies_len, bool add_mdie); diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_dpp.c b/components/wpa_supplicant/esp_supplicant/src/esp_dpp.c index ae168061489..ce35e65fb28 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_dpp.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_dpp.c @@ -18,6 +18,7 @@ #include "common/ieee802_11_common.h" #include "esp_wps_i.h" #include "rsn_supp/wpa.h" +#include "rsn_supp/wpa_i.h" #include "rsn_supp/pmksa_cache.h" #include #include @@ -39,6 +40,7 @@ static void *s_dpp_api_lock = NULL; static void *s_dpp_event_group = NULL; #define DPP_ROC_EVENT_HANDLED BIT0 +#define DPP_MAX_COMEBACK_DELAY_TU 5000 /* ~5 seconds */ static atomic_bool roc_in_progress; static atomic_bool dpp_shutting_down; @@ -60,6 +62,23 @@ static void esp_dpp_peer_disc_retry(void *eloop_ctx, void *timeout_ctx); static void esp_dpp_gas_query_req_retry(void *eloop_ctx, void *timeout_ctx); static esp_err_t esp_dpp_start_net_intro_protocol_internal(uint8_t *bssid); static void dpp_stop_internal(void); +static esp_err_t gas_query_req_tx(struct dpp_authentication *auth); +static void gas_query_timeout(void *eloop_data, void *user_ctx); +static void peer_disc_timeout(void *eloop_data, void *user_ctx); + +static bool esp_dpp_stored_conf_matches_row(struct dpp_config_store *dc, + const esp_dpp_config_data_t *row); +static esp_err_t esp_dpp_conf_alloc_from_config_data(const esp_dpp_config_data_t *config, + struct dpp_conf **out_conf); +static struct dpp_conf *esp_dpp_config_store_get_selected_entry(void); + +static void dpp_gas_cleanup_locked(void) +{ + wpabuf_clear_free(s_dpp_ctx.gas_resp_buf); + s_dpp_ctx.gas_resp_buf = NULL; + s_dpp_ctx.gas_wait_comeback = false; + s_dpp_ctx.gas_frag_id = 0; +} static esp_err_t dpp_api_lock(void) { @@ -95,7 +114,7 @@ static int listen_stop_handler(void *data, void *user_ctx) { wifi_roc_req_t req = {0}; - if (!atomic_load(&s_dpp_init_done) || atomic_load(&dpp_shutting_down)) { + if (!atomic_load(&s_dpp_init_done)) { return 0; } @@ -114,6 +133,7 @@ static int listen_stop_handler(void *data, void *user_ctx) static void dpp_stop_internal(void) { dpp_cancel_auth_gas_eloop_timeouts(); + dpp_gas_cleanup_locked(); if (s_dpp_ctx.dpp_auth) { dpp_deinit_auth(); @@ -186,7 +206,8 @@ esp_err_t esp_dpp_send_action_frame(uint8_t *dest_mac, const uint8_t *buf, uint3 req->channel = channel; req->sec_channel = WIFI_SECOND_CHAN_NONE; req->wait_time_ms = wait_time_ms; - req->type = WIFI_OFFCHAN_TX_REQ; + req->type = (type == DPP_TX_PEER_DISCOVERY_REQ) ? WIFI_OFFCHAN_TX_CONNECTING_REQ + : WIFI_OFFCHAN_TX_REQ; os_memcpy(req->data, buf, req->data_len); wpa_printf(MSG_DEBUG, "DPP: Mgmt Tx - MAC:" MACSTR ", Channel-%d, WaitT-%d, Type-%d", @@ -321,126 +342,6 @@ static esp_err_t esp_dpp_rx_auth_req(struct action_rx_param *rx_param, uint8_t * return ESP_OK; } - -static void gas_query_timeout(void *eloop_data, void *user_ctx) -{ - struct dpp_authentication *auth = user_ctx; - - /* Prerequisite confirmed via lifecycle (timers cancelled on deinit), lock acquisition will not fail. */ - dpp_api_lock(); - - if (atomic_load(&dpp_shutting_down)) { - dpp_api_unlock(); - return; - } - - if (!s_dpp_ctx.dpp_auth || !s_dpp_ctx.dpp_auth->auth_success || (s_dpp_ctx.dpp_auth != auth)) { - wpa_printf(MSG_INFO, "DPP-GAS: Auth %p state not correct", auth); - dpp_api_unlock(); - return; - } - - wpa_printf(MSG_DEBUG, "GAS: No response received for GAS query"); - dpp_abort_failure_locked(ESP_ERR_DPP_CONF_TIMEOUT); -} - -static int gas_query_req_tx(struct dpp_authentication *auth) -{ - struct wpabuf *buf; - int supp_op_classes[] = {81, 0}; - int ret; - - wpabuf_free(auth->conf_req); - auth->conf_req = NULL; - - buf = dpp_build_conf_req_helper(auth, NULL, 0, NULL, - supp_op_classes); - if (!buf) { - wpa_printf(MSG_ERROR, "DPP: No configuration request data available"); - return ESP_ERR_DPP_FAILURE; - } - - wpa_printf(MSG_INFO, "DPP: GAS request to " MACSTR " (chan %u)", - MAC2STR(auth->peer_mac_addr), auth->curr_chan); - - ret = esp_dpp_send_action_frame(auth->peer_mac_addr, wpabuf_head(buf), wpabuf_len(buf), - auth->curr_chan, 1000 + OFFCHAN_TX_WAIT_TIME, - DPP_TX_GAS_CONFIG_REQ); - if (ret != ESP_OK) { - wpabuf_free(buf); - return ret; - } - - auth->conf_req = buf; - if (eloop_register_timeout(2, 0, gas_query_timeout, NULL, auth) < 0) { - wpa_printf(MSG_ERROR, "DPP: Failed to register gas_query_timeout"); - wpabuf_free(auth->conf_req); - auth->conf_req = NULL; - return ESP_ERR_NO_MEM; - } - - return ret; -} - -static esp_err_t esp_dpp_handle_config_obj(struct dpp_authentication *auth, - struct dpp_config_obj *conf) -{ - wifi_config_t *wifi_cfg = &s_dpp_ctx.wifi_cfg; - os_memset(wifi_cfg, 0, sizeof(wifi_config_t)); - - if (conf->ssid_len) { - os_memcpy(wifi_cfg->sta.ssid, conf->ssid, conf->ssid_len); - } - - if (dpp_akm_legacy(conf->akm)) { - if (conf->passphrase[0]) - os_memcpy(wifi_cfg->sta.password, conf->passphrase, - sizeof(wifi_cfg->sta.password)); - if (conf->akm == DPP_AKM_PSK_SAE) { - wifi_cfg->sta.pmf_cfg.required = true; - } - } - - if (conf->connector) { - /* TODO: Save the Connector and consider using a command - * to fetch the value instead of sending an event with - * it. The Connector could end up being larger than what - * most clients are receive as an event - * message. */ - wpa_printf(MSG_INFO, DPP_EVENT_CONNECTOR "%s", - conf->connector); - } - if (atomic_load(&roc_in_progress)) { - listen_stop_handler(NULL, NULL); - } - - wifi_event_dpp_config_received_t event = {0}; - event.wifi_cfg = *wifi_cfg; - - dpp_api_unlock(); - esp_err_t ret = esp_event_post(WIFI_EVENT, WIFI_EVENT_DPP_CFG_RECVD, &event, sizeof(event), - os_task_ms_to_tick(200)); - - /* Prerequisite confirmed via atomic check at entry point, lock re-acquisition will not fail. */ - dpp_api_lock(); - - /* Re-verify auth context: another task (e.g. deinit) could have freed s_dpp_ctx.dpp_auth - * while the lock was released. Note: ABA (pointer reuse) is not possible here because - * dpp_auth allocation is strictly serialized on the eloop task, which is currently - * occupied by this function. A pointer mismatch is sufficient to detect invalidation. */ - if (!atomic_load(&s_dpp_init_done) || atomic_load(&dpp_shutting_down) || s_dpp_ctx.dpp_auth != auth) { - wpa_printf(MSG_ERROR, "DPP: Invalid state after relock in config handling - aborting"); - return ESP_ERR_INVALID_STATE; - } - - if (ret != ESP_OK) { - wpa_printf(MSG_ERROR, "DPP: Failed to post DPP_CFG_RECVD event, error 0x%x", ret); - return ret; - } - - return ESP_OK; -} - static int esp_dpp_rx_auth_conf(struct action_rx_param *rx_param, uint8_t *dpp_data) { struct dpp_authentication *auth = s_dpp_ctx.dpp_auth; @@ -471,9 +372,11 @@ static int esp_dpp_rx_auth_conf(struct action_rx_param *rx_param, uint8_t *dpp_d return ESP_ERR_DPP_FAILURE; } - /* Send GAS Query Req. Reset retry counter: GAS Config is a new phase - * with its own budget across retransmits. */ + /* Fresh GAS query for this session: reset retry budget so a previous + * partial run cannot eat into this session's retries. The retry path + * (esp_dpp_gas_query_req_retry) bumps the counter itself. */ s_dpp_ctx.gas_query_tries = 0; + rc = gas_query_req_tx(auth); if (rc != ESP_OK) { wpa_printf(MSG_ERROR, "DPP: GAS query Tx failed"); @@ -485,148 +388,135 @@ static int esp_dpp_rx_auth_conf(struct action_rx_param *rx_param, uint8_t *dpp_d static esp_err_t esp_dpp_rx_peer_disc_resp(struct action_rx_param *rx_param) { - struct dpp_authentication *auth = s_dpp_ctx.dpp_auth; + struct dpp_config_store *dc = s_dpp_ctx.dpp_config_store; uint8_t *buf; - unsigned int seconds; - struct os_reltime rnow; - const uint8_t *connector, *trans_id, *status = NULL; - uint16_t connector_len, trans_id_len, status_len; - enum dpp_status_error res = DPP_STATUS_NOT_COMPATIBLE; - struct dpp_introduction intro; - os_time_t expiry; - struct os_time now; - struct wpa_sm *sm = get_wpa_sm(); - struct rsn_pmksa_cache_entry *entry = NULL; - int i = 0; - if (!rx_param) { - return ESP_ERR_INVALID_ARG; - } - - if (rx_param->vendor_data_len < 2) { - /* esp_dpp_rx_frm already guards length; keep ESP_OK so spoofed frames - * cannot abort the session (same rationale as esp_dpp_rx_frm / GAS). */ - wpa_printf(MSG_DEBUG, "DPP: Too short vendor specific data in peer discovery response"); - return ESP_OK; + if (!dc) { + return ESP_ERR_DPP_FAILURE; } + const uint8_t *trans_id; + uint16_t trans_id_len; size_t len = rx_param->vendor_data_len - 2; buf = rx_param->action_frm->u.public_action.v.pa_vendor_spec.vendor_data; - if (!auth) { - wpa_printf(MSG_DEBUG, "DPP: Auth context not found for Peer Discovery response"); - return ESP_ERR_INVALID_STATE; - } - - if (os_memcmp(auth->peer_mac_addr, rx_param->sa, ETH_ALEN) != 0) { + if (os_memcmp(dc->peer_mac_addr, rx_param->sa, ETH_ALEN) != 0) { wpa_printf(MSG_DEBUG, "DPP: Not expecting Peer Discovery response from " MACSTR, MAC2STR(rx_param->sa)); return ESP_OK; } wpa_printf(MSG_DEBUG, "DPP: Peer Discovery from " MACSTR, MAC2STR(rx_param->sa)); - for (i = 0; i < auth->num_conf_obj; i++) { - - if (!auth->conf_obj[i].connector - || !auth->net_access_key - || !auth->conf_obj[i].c_sign_key - || dpp_akm_legacy(auth->conf_obj[i].akm)) { - wpa_printf(MSG_DEBUG, "DPP: Profile not found for network introduction or akm mismatch"); - continue; - } - - trans_id = dpp_get_attr(&buf[2], len, DPP_ATTR_TRANSACTION_ID, &trans_id_len); - if (!trans_id || trans_id_len != 1) { - wpa_printf(MSG_ERROR, "DPP: Peer did not include Transaction ID"); - return ESP_ERR_DPP_FAILURE; - } - if (trans_id[0] != TRANSACTION_ID) { - wpa_printf(MSG_ERROR, "DPP: Ignore frame with unexpected Transaction ID %u", trans_id[0]); - return ESP_ERR_DPP_FAILURE; - } - - status = dpp_get_attr(&buf[2], len, DPP_ATTR_STATUS, &status_len); - if (!status || status_len != 1) { - wpa_printf(MSG_ERROR, "DPP: Peer did not include Status"); - return ESP_ERR_DPP_FAILURE; - } - if (status[0] != DPP_STATUS_OK) { - wpa_printf(MSG_ERROR, "DPP: Peer rejected network introduction: Status %u", status[0]); - return ESP_ERR_DPP_FAILURE; - } - - connector = dpp_get_attr(&buf[2], len, DPP_ATTR_CONNECTOR, &connector_len); - if (!connector) { - wpa_printf(MSG_ERROR, "DPP: Peer did not include its Connector"); - return ESP_ERR_DPP_FAILURE; - } - - res = dpp_peer_intro(&intro, auth->conf_obj[i].connector, - wpabuf_head(auth->net_access_key), - wpabuf_len(auth->net_access_key), - wpabuf_head(auth->conf_obj[i].c_sign_key), - wpabuf_len(auth->conf_obj[i].c_sign_key), - connector, connector_len, &expiry); - - if (res == DPP_STATUS_OK) { - entry = os_zalloc(sizeof(*entry)); - if (!entry) { - goto fail; - } - os_memcpy(entry->aa, rx_param->sa, ETH_ALEN); - os_memcpy(entry->pmkid, intro.pmkid, PMKID_LEN); - os_memcpy(entry->pmk, intro.pmk, intro.pmk_len); - entry->pmk_len = intro.pmk_len; - entry->akmp = WPA_KEY_MGMT_DPP; - - if (expiry) { - os_get_time(&now); - if (expiry > now.sec) { - seconds = expiry - now.sec; - } else { - wpa_printf(MSG_WARNING, "DPP: Connector expired during processing"); - goto fail; - } - } else { - seconds = ESP_DPP_PMK_CACHE_DEFAULT_TIMEOUT; - } - os_get_reltime(&rnow); - entry->expiration = rnow.sec + seconds; - entry->reauth_time = rnow.sec + seconds; - entry->network_ctx = NULL; - - wpa_printf(MSG_INFO, "peer=" MACSTR " status=%u", MAC2STR(rx_param->sa), status[0]); - break; - } + trans_id = dpp_get_attr(&buf[2], len, DPP_ATTR_TRANSACTION_ID, &trans_id_len); + if (!trans_id || trans_id_len != 1) { + wpa_printf(MSG_ERROR, "DPP: Peer did not include Transaction ID"); + return ESP_ERR_DPP_INVALID_ATTR; + } + if (trans_id[0] != TRANSACTION_ID) { + wpa_printf(MSG_DEBUG, "DPP: Ignore frame with unexpected Transaction ID %u", trans_id[0]); + return ESP_OK; } - if (res != DPP_STATUS_OK) { - wpa_printf(MSG_ERROR, "DPP: Network Introduction protocol resulted in failure"); - goto fail; + const uint8_t *status_val; + uint16_t status_len; + const uint8_t *peer_connector; + uint16_t peer_connector_len; + struct dpp_introduction *intro = NULL; + os_time_t expiry = 0; + struct dpp_conf *conf = dc->conf; + + if (!conf) { + wpa_printf(MSG_ERROR, "DPP: No active configuration to derive PMK"); + return ESP_ERR_DPP_FAILURE; } - wpa_printf(MSG_DEBUG, - "DPP: Try connection after successful network introduction"); - int rc = dpp_connect(rx_param->sa, true); - if (rc != ESP_OK) { - goto fail; + intro = os_zalloc(sizeof(*intro)); + if (!intro) { + wpa_printf(MSG_ERROR, "DPP: Failed to allocate memory for intro"); + return ESP_ERR_NO_MEM; } - /* Connection initiated! Now we commit the PMK to the global cache. */ - pmksa_cache_add_entry(sm->pmksa, entry); - entry = NULL; + esp_err_t ret = ESP_OK; - /* Final cleanup: connection initiated, auth context and timers no longer needed. */ - dpp_stop_internal(); - forced_memzero(&intro, sizeof(intro)); - - return ESP_OK; -fail: - forced_memzero(&intro, sizeof(intro)); - if (entry != NULL) { - bin_clear_free(entry, sizeof(*entry)); + status_val = dpp_get_attr(&buf[2], len, DPP_ATTR_STATUS, &status_len); + if (!status_val || status_len != 1 || status_val[0] != DPP_STATUS_OK) { + wpa_printf(MSG_ERROR, "DPP: Peer Discovery failed or bad status (status: %d)", status_val ? status_val[0] : -1); + ret = ESP_ERR_DPP_FAILURE; + goto out; } - return ESP_ERR_DPP_FAILURE; + + peer_connector = dpp_get_attr(&buf[2], len, DPP_ATTR_CONNECTOR, &peer_connector_len); + if (!peer_connector) { + wpa_printf(MSG_ERROR, "DPP: Peer did not include Connector"); + ret = ESP_ERR_DPP_INVALID_ATTR; + goto out; + } + + /* peer_disc_timeout handles timeout in Enrollee role */ + eloop_cancel_timeout(peer_disc_timeout, NULL, s_dpp_ctx.dpp_auth); + + if (!conf->connector || !conf->net_access_key || !conf->c_sign_key) { + wpa_printf(MSG_ERROR, "DPP: Incomplete config for network introduction"); + ret = ESP_ERR_DPP_FAILURE; + goto out; + } + + if (dpp_peer_intro(intro, conf->connector, + wpabuf_head(conf->net_access_key), wpabuf_len(conf->net_access_key), + wpabuf_head(conf->c_sign_key), wpabuf_len(conf->c_sign_key), + peer_connector, peer_connector_len, &expiry) != DPP_STATUS_OK) { + wpa_printf(MSG_ERROR, "DPP: Failed to derive PMK from Peer Discovery Response"); + ret = ESP_ERR_DPP_FAILURE; + goto out; + } + + struct rsn_pmksa_cache_entry *entry; + struct os_reltime now_rel; + os_time_t seconds; + + entry = os_zalloc(sizeof(*entry)); + if (!entry) { + wpa_printf(MSG_ERROR, "DPP: Failed to allocate PMKSA entry"); + ret = ESP_ERR_NO_MEM; + goto out; + } + + os_memcpy(entry->aa, rx_param->sa, ETH_ALEN); + os_memcpy(entry->pmkid, intro->pmkid, PMKID_LEN); + os_memcpy(entry->pmk, intro->pmk, intro->pmk_len); + entry->pmk_len = intro->pmk_len; + entry->akmp = WPA_KEY_MGMT_DPP; + entry->network_ctx = NULL; + + if (expiry > 0) { + struct os_time now; + os_get_time(&now); + if (expiry > now.sec) { + seconds = expiry - now.sec; + } else { + wpa_printf(MSG_WARNING, "DPP: Connector expired during processing"); + bin_clear_free(entry, sizeof(*entry)); + ret = ESP_ERR_DPP_FAILURE; + goto out; + } + } else { + seconds = ESP_DPP_PMK_CACHE_DEFAULT_TIMEOUT; + } + + os_get_reltime(&now_rel); + entry->expiration = now_rel.sec + seconds; + entry->reauth_time = entry->expiration; + + pmksa_cache_add_entry(gWpaSm.pmksa, entry); + + if (dpp_connect(rx_param->sa, true) != ESP_OK) { + wpa_printf(MSG_ERROR, "DPP: Failed to trigger connection after Peer Discovery"); + ret = ESP_ERR_DPP_FAILURE; + } + +out: + bin_clear_free(intro, sizeof(*intro)); + return ret; } static esp_err_t esp_dpp_rx_frm(struct action_rx_param *rx_param) @@ -635,20 +525,19 @@ static esp_err_t esp_dpp_rx_frm(struct action_rx_param *rx_param) uint8_t *tmp; int ret = ESP_OK; - if (rx_param->vendor_data_len < 2) { - /* Note: We do not return an error or abort the session on data validation - * failures to avoid interruption from spoofed frames. */ - wpa_printf(MSG_ERROR, "DPP: Vendor data too short (%u)", (unsigned)rx_param->vendor_data_len); - return ESP_OK; - } - tmp = rx_param->action_frm->u.public_action.v.pa_vendor_spec.vendor_data; + if (rx_param->vendor_data_len < 2) { /* vendor_data too short for 2-octet DPP header before attr buffer */ + wpa_printf(MSG_DEBUG, + "DPP: vendor-specific data too short (len=%u), dropping", + (unsigned)rx_param->vendor_data_len); + return ESP_ERR_DPP_INVALID_ATTR; + } crypto_suit = tmp[0]; type = tmp[1]; if (crypto_suit != 1) { wpa_printf(MSG_ERROR, "DPP: Unsupported crypto suit"); - return ESP_OK; + return ESP_ERR_DPP_INVALID_ATTR; } switch (type) { @@ -661,18 +550,464 @@ static esp_err_t esp_dpp_rx_frm(struct action_rx_param *rx_param) case DPP_PA_PEER_DISCOVERY_RESP: ret = esp_dpp_rx_peer_disc_resp(rx_param); break; + default: + wpa_printf(MSG_DEBUG, "DPP: ignore unhandled DPP public action type %u", type); + break; } return ret; } +static void peer_disc_timeout(void *eloop_data, void *user_ctx) +{ + /* Note: user_ctx (auth) is guaranteed valid and not a use-after-free risk. + * The eloop is single-threaded, and pending timeouts are synchronously cancelled + * via dpp_cancel_auth_gas_eloop_timeouts() during teardown before auth is freed. */ + struct dpp_authentication *auth = user_ctx; + + /* Prerequisite confirmed via lifecycle (timers cancelled on deinit), lock acquisition will not fail. */ + dpp_api_lock(); + + if (atomic_load(&dpp_shutting_down)) { + dpp_api_unlock(); + return; + } + + if (!s_dpp_ctx.dpp_auth || (s_dpp_ctx.dpp_auth != auth)) { + wpa_printf(MSG_INFO, "DPP: Auth %p state not correct for peer discovery", auth); + dpp_api_unlock(); + return; + } + + wpa_printf(MSG_DEBUG, "DPP: No response received for Peer Discovery Request"); + dpp_abort_failure_locked(ESP_ERR_DPP_CONF_TIMEOUT); +} + +static void gas_query_timeout(void *eloop_data, void *user_ctx) +{ + /* Note: user_ctx (auth) is guaranteed valid and not a use-after-free risk. + * The eloop is single-threaded, and pending timeouts are synchronously cancelled + * via dpp_cancel_auth_gas_eloop_timeouts() during teardown before auth is freed. */ + struct dpp_authentication *auth = user_ctx; + + /* Prerequisite confirmed via lifecycle (timers cancelled on deinit), lock acquisition will not fail. */ + dpp_api_lock(); + + if (atomic_load(&dpp_shutting_down)) { + dpp_api_unlock(); + return; + } + + if (!s_dpp_ctx.dpp_auth || !s_dpp_ctx.dpp_auth->auth_success || (s_dpp_ctx.dpp_auth != auth)) { + wpa_printf(MSG_INFO, "DPP-GAS: Auth %p state not correct", auth); + dpp_api_unlock(); + return; + } + + wpa_printf(MSG_DEBUG, "GAS: No response received for GAS query"); + dpp_abort_failure_locked(ESP_ERR_DPP_CONF_TIMEOUT); +} + +static esp_err_t gas_query_req_tx(struct dpp_authentication *auth) +{ + struct wpabuf *buf; + int supp_op_classes[] = {81, 0}; + esp_err_t ret; + + wpabuf_free(auth->conf_req); + auth->conf_req = NULL; + + buf = dpp_build_conf_req_helper(auth, NULL, 0, NULL, + supp_op_classes); + if (!buf) { + wpa_printf(MSG_ERROR, "DPP: No configuration request data available"); + return ESP_ERR_DPP_FAILURE; + } + + wpa_printf(MSG_INFO, "DPP: GAS request to " MACSTR " (chan %u)", + MAC2STR(auth->peer_mac_addr), auth->curr_chan); + + ret = esp_dpp_send_action_frame(auth->peer_mac_addr, wpabuf_head(buf), wpabuf_len(buf), + auth->curr_chan, 1000 + OFFCHAN_TX_WAIT_TIME, + DPP_TX_GAS_CONFIG_REQ); + if (ret != ESP_OK) { + wpabuf_free(buf); + return ret; + } + + auth->conf_req = buf; + if (eloop_register_timeout(ESP_GAS_TIMEOUT_SECS, 0, gas_query_timeout, NULL, auth) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to register gas_query_timeout"); + wpabuf_free(auth->conf_req); + auth->conf_req = NULL; + return ESP_ERR_NO_MEM; + } + + return ret; +} + +static void esp_dpp_process_config_obj(esp_dpp_config_data_t *config_data, struct dpp_config_obj *conf) +{ + if (conf->ssid_len) { + size_t n = conf->ssid_len; + + if (n > MAX_SSID_LEN) { + n = MAX_SSID_LEN; + wpa_printf(MSG_WARNING, "DPP: SSID truncated to %u octets", MAX_SSID_LEN); + } + config_data->ssid_len = (uint8_t)n; + os_memcpy(config_data->ssid, conf->ssid, n); + } + + if (dpp_akm_legacy(conf->akm) || dpp_akm_ver2(conf->akm)) { + if (conf->passphrase[0]) { + size_t pass_len = os_strnlen(conf->passphrase, sizeof(conf->passphrase)); + + if (pass_len > MAX_PASSPHRASE_LEN) { + wpa_printf(MSG_WARNING, + "DPP: passphrase length %zu exceeds MAX_PASSPHRASE_LEN, truncating", + pass_len); + pass_len = MAX_PASSPHRASE_LEN; + } + os_memcpy(config_data->password, conf->passphrase, pass_len); + config_data->password_len = (uint8_t) pass_len; + } else if (conf->psk_set) { + char hex_tmp[MAX_PASSPHRASE_LEN + 1]; + int hex_chars; + + hex_chars = wpa_snprintf_hex(hex_tmp, sizeof(hex_tmp), conf->psk, PMK_LEN); + if (hex_chars != (int)(PMK_LEN * 2)) { + wpa_hexdump_key(MSG_ERROR, + "DPP: PSK hex conversion failed (unexpected length)", + conf->psk, PMK_LEN); + forced_memzero(hex_tmp, sizeof(hex_tmp)); + } else { + os_memcpy(config_data->password, hex_tmp, PMK_LEN * 2); + config_data->password_len = PMK_LEN * 2; + forced_memzero(hex_tmp, sizeof(hex_tmp)); + } + } + } +} + +static int esp_dpp_handle_config_obj(struct dpp_authentication *auth, + struct dpp_config_obj *conf, esp_dpp_config_data_t *config_data) +{ + forced_memzero(config_data, sizeof(*config_data)); + + if (conf->connector) { + size_t n; + + wpa_printf(MSG_INFO, DPP_EVENT_CONNECTOR "%s", + conf->connector); + n = os_strlcpy(config_data->connector, conf->connector, + sizeof(config_data->connector)); + if (n >= sizeof(config_data->connector)) { + wpa_printf(MSG_WARNING, + "DPP: Connector length %zu exceeds storage (%zu); " + "truncated JWS cannot be used, rejecting configuration object", + n, sizeof(config_data->connector)); + forced_memzero(config_data, sizeof(*config_data)); + return -1; + } + config_data->connector_len = (uint16_t)n; + } else { + config_data->connector_len = 0; + } + + if (auth->net_access_key && dpp_akm_dpp(conf->akm)) { + size_t key_len = wpabuf_len(auth->net_access_key); + if (key_len > ESP_DPP_MAX_KEY_LEN) { + key_len = ESP_DPP_MAX_KEY_LEN; + } + os_memcpy(config_data->net_access_key, wpabuf_head(auth->net_access_key), key_len); + config_data->net_access_key_len = (uint16_t)key_len; + config_data->net_access_key_expiry = auth->net_access_key_expiry; + } + + if (conf->c_sign_key) { + size_t key_len = wpabuf_len(conf->c_sign_key); + if (key_len > ESP_DPP_MAX_KEY_LEN) { + key_len = ESP_DPP_MAX_KEY_LEN; + } + os_memcpy(config_data->c_sign_key, wpabuf_head(conf->c_sign_key), key_len); + config_data->c_sign_key_len = (uint16_t)key_len; + } + + config_data->curr_chan = (uint8_t)auth->curr_chan; + config_data->akm = (uint8_t)conf->akm; + + esp_dpp_process_config_obj(config_data, conf); + + return 0; +} + +static void esp_dpp_fill_wifi_cfg_from_config(const esp_dpp_config_data_t *dpp, wifi_config_t *wifi_cfg) +{ + size_t ssid_len; + size_t pass_len; + + forced_memzero(wifi_cfg, sizeof(*wifi_cfg)); + if (!dpp) { + return; + } + + if (dpp->ssid_len) { + ssid_len = dpp->ssid_len; + if (ssid_len > sizeof(wifi_cfg->sta.ssid)) { + ssid_len = sizeof(wifi_cfg->sta.ssid); + } + os_memcpy(wifi_cfg->sta.ssid, dpp->ssid, ssid_len); + } else if (dpp->ssid[0]) { + /* Fallback for legacy configs that don't set ssid_len */ + ssid_len = os_strnlen((const char *) dpp->ssid, sizeof(dpp->ssid)); + os_memcpy(wifi_cfg->sta.ssid, dpp->ssid, ssid_len); + } + + if (dpp_akm_legacy((enum dpp_akm) dpp->akm) || dpp_akm_ver2((enum dpp_akm) dpp->akm)) { + pass_len = dpp->password_len; + if (pass_len == 0 && dpp->password[0]) { + /* Fallback for legacy configs */ + pass_len = os_strnlen((const char *) dpp->password, sizeof(dpp->password)); + } + if (pass_len > MAX_PASSPHRASE_LEN) { + wpa_printf(MSG_WARNING, + "DPP: password_len %zu exceeds MAX_PASSPHRASE_LEN, clamping", + pass_len); + pass_len = MAX_PASSPHRASE_LEN; + } + if (pass_len > sizeof(wifi_cfg->sta.password)) { + pass_len = sizeof(wifi_cfg->sta.password); + } + if (pass_len) { + os_memcpy(wifi_cfg->sta.password, dpp->password, pass_len); + } + } + + if (dpp_akm_sae((enum dpp_akm) dpp->akm)) { + wifi_cfg->sta.pmf_cfg.capable = true; + wifi_cfg->sta.pmf_cfg.required = true; + } + + if (dpp->curr_chan) { + wifi_cfg->sta.channel = dpp->curr_chan; + } +} + +static esp_err_t gas_process_complete_resp(struct dpp_authentication *auth, + const uint8_t *resp, size_t dpp_data_len) +{ + int i, res; + uint8_t conf_capacity = 0; + size_t recv_size = 0; + size_t post_size = 0; + struct dpp_conf *new_conf_pending = NULL; + wifi_event_dpp_config_received_t *recv = NULL; + esp_err_t ret = ESP_ERR_DPP_FAILURE; + + if (dpp_conf_resp_rx(auth, resp, dpp_data_len) < 0) { + wpa_printf(MSG_INFO, "DPP: Configuration attempt failed"); + return ESP_ERR_DPP_FAILURE; + } + + /* Configuration parsed successfully — cancel the GAS response timeout. + * Without this, gas_query_timeout would fire a few seconds later and + * post a spurious WIFI_EVENT_DPP_FAILED after the app already received + * WIFI_EVENT_DPP_CFG_RECVD. */ + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + + if (auth->num_conf_obj <= 0) { + conf_capacity = 0; + } else if (auth->num_conf_obj > ESP_DPP_MAX_CONFIG_COUNT) { + conf_capacity = ESP_DPP_MAX_CONFIG_COUNT; + } else { + conf_capacity = (uint8_t)auth->num_conf_obj; + } + recv_size = sizeof(*recv) + conf_capacity * sizeof(recv->configs[0]); + recv = os_zalloc(recv_size); + if (!recv) { + wpa_printf(MSG_ERROR, "DPP: Failed to allocate memory for DPP event"); + return ESP_ERR_NO_MEM; + } + + /* + * Runs on eloop only. + * A candidate configuration is built into new_conf_pending and committed to dc->conf only + * after esp_event_post succeeds (commit-at-end). We track old_conf across the unlock + * window to prevent overwriting a configuration set concurrently by the app. + */ + if (auth->num_conf_obj > ESP_DPP_MAX_CONFIG_COUNT) { + wpa_printf(MSG_WARNING, + "DPP: %d config objects in response, forwarding first %d and dropping %d", + auth->num_conf_obj, ESP_DPP_MAX_CONFIG_COUNT, + auth->num_conf_obj - ESP_DPP_MAX_CONFIG_COUNT); + } + + for (i = 0; i < auth->num_conf_obj && recv->total_conf < conf_capacity; i++) { + uint8_t idx = recv->total_conf; + + res = esp_dpp_handle_config_obj(auth, &auth->conf_obj[i], &recv->configs[idx]); + if (res < 0) { + wpa_printf(MSG_WARNING, "DPP: Configuration storage failed for object %d; not counting in total_conf", i); + continue; + } + + recv->total_conf++; + } + + if (recv->total_conf > 0) { + esp_dpp_config_data_t *first_conf = &recv->configs[0]; + + esp_dpp_fill_wifi_cfg_from_config(first_conf, &recv->wifi_cfg); + + if (dpp_akm_dpp((enum dpp_akm) first_conf->akm) && + (first_conf->connector_len > 0)) { + if (s_dpp_ctx.dpp_config_store) { + struct dpp_config_store *dc = s_dpp_ctx.dpp_config_store; + + if (esp_dpp_stored_conf_matches_row(dc, first_conf)) { + /* Same as installed config; nothing new to store. */ + } else { + esp_err_t autostore = esp_dpp_conf_alloc_from_config_data(first_conf, + &new_conf_pending); + + if (autostore != ESP_OK) { + wpa_printf(MSG_WARNING, + "DPP: auto-load of first configuration object for supplicant failed: %d", + (int) autostore); + goto out; + } + } + } + } else if (!dpp_akm_dpp((enum dpp_akm) first_conf->akm) && + s_dpp_ctx.dpp_config_store && s_dpp_ctx.dpp_config_store->conf) { + /* Replaced by a non-DPP AKM (legacy PSK/SAE): drop stored DPP + * config so it is not reused. A malformed DPP-AKM config with + * no Connector is rejected without touching the stored entry. */ + dpp_clear_confs(s_dpp_ctx.dpp_config_store->conf); + s_dpp_ctx.dpp_config_store->conf = NULL; + } + } + + if (recv->total_conf == 0) { + wpa_printf(MSG_ERROR, "DPP: No valid configuration object was processed"); + goto out; + } + + post_size = sizeof(*recv) + recv->total_conf * sizeof(recv->configs[0]); + struct dpp_conf *old_conf = s_dpp_ctx.dpp_config_store ? s_dpp_ctx.dpp_config_store->conf : NULL; + dpp_api_unlock(); + + if (esp_event_post(WIFI_EVENT, WIFI_EVENT_DPP_CFG_RECVD, recv, post_size, os_task_ms_to_tick(200)) != ESP_OK) { + wpa_printf(MSG_ERROR, "DPP: Failed to post WIFI_EVENT_DPP_CFG_RECVD"); + /* Prerequisite confirmed via atomic check at entry, lock re-acquisition will not fail. */ + dpp_api_lock(); + goto out; + } + + /* Prerequisite confirmed via atomic check at entry, lock re-acquisition will not fail. */ + dpp_api_lock(); + + /* Re-verify state: another task (e.g. deinit) could have run while the lock + * was released. If DPP was shut down, skip the config store commit. */ + if (atomic_load(&dpp_shutting_down) || !atomic_load(&s_dpp_init_done)) { + wpa_printf(MSG_WARNING, "DPP: State changed during event post, skipping config store commit"); + ret = ESP_ERR_INVALID_STATE; + goto out; + } + + if (new_conf_pending) { + if (s_dpp_ctx.dpp_config_store) { + struct dpp_config_store *dc = s_dpp_ctx.dpp_config_store; + + if (dc->conf != old_conf) { + wpa_printf(MSG_INFO, "DPP: Configuration updated by another task during event post, dropping new config"); + dpp_clear_confs(new_conf_pending); + } else { + dpp_clear_confs(dc->conf); + dc->conf = new_conf_pending; + } + } else { + dpp_clear_confs(new_conf_pending); + } + new_conf_pending = NULL; + } + + ret = ESP_OK; + +out: + bin_clear_free(recv, recv_size); + dpp_clear_confs(new_conf_pending); + return ret; +} + +static esp_err_t gas_comeback_req_tx(struct dpp_authentication *auth) +{ + struct wpabuf *buf; + esp_err_t ret; + + buf = gas_build_comeback_req(s_dpp_ctx.gas_dialog_token); + if (!buf) { + wpa_printf(MSG_ERROR, "DPP: Failed to build GAS Comeback Request"); + return ESP_ERR_DPP_FAILURE; + } + + wpa_printf(MSG_INFO, "DPP: GAS Comeback Request to " MACSTR " (chan %u)", + MAC2STR(auth->peer_mac_addr), auth->curr_chan); + + ret = esp_dpp_send_action_frame(auth->peer_mac_addr, + wpabuf_head(buf), wpabuf_len(buf), + auth->curr_chan, + 1000 + OFFCHAN_TX_WAIT_TIME, + DPP_TX_GAS_COMEBACK); + wpabuf_free(buf); + + if (ret == ESP_OK) { + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + if (eloop_register_timeout(ESP_GAS_TIMEOUT_SECS, 0, + gas_query_timeout, NULL, auth) != 0) { + return ESP_ERR_DPP_FAILURE; + } + } + + return ret; +} + +static void gas_comeback_delay_timeout(void *eloop_data, void *user_ctx) +{ + /* Note: user_ctx (auth) is guaranteed valid and not a use-after-free risk. + * The eloop is single-threaded, and pending timeouts are synchronously cancelled + * via dpp_cancel_auth_gas_eloop_timeouts() during teardown before auth is freed. */ + struct dpp_authentication *auth = user_ctx; + + if (dpp_api_lock() != ESP_OK) { + return; + } + + if (atomic_load(&dpp_shutting_down)) { + dpp_api_unlock(); + return; + } + + if (!s_dpp_ctx.dpp_auth || s_dpp_ctx.dpp_auth != auth || + !s_dpp_ctx.dpp_auth->auth_success) { + dpp_api_unlock(); + return; + } + if (gas_comeback_req_tx(auth) != ESP_OK) { + dpp_abort_failure_locked(ESP_ERR_DPP_TX_FAILURE); + return; + } + dpp_api_unlock(); +} + static esp_err_t gas_query_resp_rx(struct action_rx_param *rx_param) { struct dpp_authentication *auth = s_dpp_ctx.dpp_auth; - uint8_t *pos = rx_param->action_frm->u.public_action.v.pa_gas_resp.data; - uint8_t *resp = &pos[10]; /* first byte of DPP attributes */ + u16 comeback_delay; + u16 status_code; + uint8_t *pos; size_t vendor_len = rx_param->vendor_data_len; - esp_err_t ret = ESP_OK; if (!auth) { return ESP_OK; @@ -684,52 +1019,246 @@ static esp_err_t gas_query_resp_rx(struct action_rx_param *rx_param) return ESP_OK; } - if (vendor_len < 2) { - /* Note: We do not return an error or abort the session on data validation - * failures (like length or structural checks) to avoid interruption from - * spoofed frames. The session will timeout naturally if a legitimate - * response is not received. */ - wpa_printf(MSG_DEBUG, "DPP: GAS response vendor data too short (%u)", - (unsigned)vendor_len); + dpp_gas_cleanup_locked(); + + if (!auth->auth_success) { + wpa_printf(MSG_DEBUG, "DPP: GAS Initial Response while DPP auth not complete; ignore"); return ESP_OK; } - /* Basic structural checks on the Advertisement Protocol payload */ - if (!(pos[1] == WLAN_EID_VENDOR_SPECIFIC && pos[2] == 5 && - WPA_GET_BE24(&pos[3]) == OUI_WFA && pos[6] == 0x1a && pos[7] == 1)) { - wpa_hexdump(MSG_INFO, "DPP: Failed, Configuration Response adv_proto", pos, 8); - return ESP_OK; - } - - /* DPP attribute length = vendor_data_len - 2, now safe after the >= 2 check above */ - size_t dpp_data_len = vendor_len - 2; - - if (dpp_conf_resp_rx(auth, resp, dpp_data_len) < 0) { - wpa_printf(MSG_INFO, "DPP: Configuration attempt failed"); - return ESP_OK; - } - - eloop_cancel_timeout(gas_query_timeout, NULL, auth); - - /* At the moment the architecture only supports one DPP configuration. - * We parse and handle the first configuration object only. */ if (auth->num_conf_obj > 0) { - ret = esp_dpp_handle_config_obj(auth, &auth->conf_obj[0]); - if (ret != ESP_OK) { - wpa_printf(MSG_INFO, "DPP: Configuration handling failed"); - goto fail; - } - - /* If legacy AKM, we are done with DPP. Stop everything. */ - if (dpp_akm_legacy(auth->conf_obj[0].akm)) { - dpp_stop_internal(); - return ESP_OK; - } + wpa_printf(MSG_DEBUG, "DPP: Already received configuration, ignoring retransmitted GAS Initial Response"); + return ESP_OK; } - return ESP_OK; + pos = rx_param->action_frm->u.public_action.v.pa_gas_resp.data; + status_code = WPA_GET_LE16((const u8 *)&rx_param->action_frm->u.public_action.v.pa_gas_resp.status_code); + comeback_delay = WPA_GET_LE16((const u8 *)&rx_param->action_frm->u.public_action.v.pa_gas_resp.comeback_delay); -fail: + if (status_code != 0) { + wpa_printf(MSG_ERROR, "DPP: GAS Initial Response failed status=%u", status_code); + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + return ESP_ERR_DPP_FAILURE; + } + + if (comeback_delay) { + if (comeback_delay > DPP_MAX_COMEBACK_DELAY_TU) { + wpa_printf(MSG_WARNING, "DPP: GAS comeback delay %u TU too large, capping to %u", + comeback_delay, DPP_MAX_COMEBACK_DELAY_TU); + comeback_delay = DPP_MAX_COMEBACK_DELAY_TU; + } + unsigned int secs = (comeback_delay * 1024) / 1000000; + unsigned int usecs = comeback_delay * 1024 - secs * 1000000; + + wpa_printf(MSG_DEBUG, "DPP: GAS comeback delay %u TUs (%u.%06u s)", + comeback_delay, secs, usecs); + + s_dpp_ctx.gas_wait_comeback = true; + s_dpp_ctx.gas_frag_id = 0; + s_dpp_ctx.gas_dialog_token = + rx_param->action_frm->u.public_action.v.pa_gas_resp.diag_token; + + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + + if (eloop_register_timeout(secs, usecs, gas_comeback_delay_timeout, NULL, auth) != 0) { + return ESP_ERR_DPP_FAILURE; + } + return ESP_OK; + } + + if (vendor_len < 8) { + wpa_printf(MSG_INFO, "DPP: Too short GAS response data (adv protocol)"); + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + return ESP_ERR_DPP_FAILURE; + } + + if (!(pos[1] == WLAN_EID_VENDOR_SPECIFIC && pos[2] == 5 && + WPA_GET_BE24(&pos[3]) == OUI_WFA && pos[6] == DPP_OUI_TYPE && pos[7] == 1)) { + wpa_hexdump(MSG_INFO, "DPP: Failed, Configuration Response adv_proto", pos, 8); + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + return ESP_ERR_DPP_FAILURE; + } + + if (vendor_len < 10) { + wpa_printf(MSG_INFO, "DPP: Too short GAS response data"); + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + return ESP_ERR_DPP_FAILURE; + } + + size_t dpp_data_len = vendor_len - 10; + uint8_t *resp = &pos[10]; + + return gas_process_complete_resp(auth, resp, dpp_data_len); +} + +static esp_err_t gas_comeback_resp_rx(struct action_rx_param *rx_param) +{ + struct dpp_authentication *auth = s_dpp_ctx.dpp_auth; + uint8_t *frame_start; + size_t frame_len; + uint8_t *pos; + u16 status_code, comeback_delay, resp_len; + u8 frag_id, more_frags; + unsigned int left; + esp_err_t ret = ESP_ERR_DPP_FAILURE; + + if (!auth || !auth->auth_success) { + return ESP_OK; + } + + if (auth->num_conf_obj > 0) { + wpa_printf(MSG_DEBUG, "DPP: Already received configuration, ignoring retransmitted GAS Comeback Response"); + return ESP_OK; + } + + /* Verify source MAC: Management frames like GAS can be easily spoofed. */ + if (os_memcmp(rx_param->sa, auth->peer_mac_addr, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "DPP: MAC mismatch on GAS Comeback Response - ignore potentially spoofed frame"); + return ESP_OK; + } + + if (!s_dpp_ctx.gas_wait_comeback) { + wpa_printf(MSG_DEBUG, "DPP: Unexpected GAS Comeback Response - ignore"); + return ESP_OK; + } + + frame_start = (uint8_t *)&rx_param->action_frm->u.public_action; + frame_len = rx_param->frm_len - + (size_t)(frame_start - (uint8_t *)rx_param->action_frm); + pos = frame_start; + + if (frame_len < 8) { /* need at least 7 for fixed fields plus 1 for adv proto element start */ + wpa_printf(MSG_DEBUG, "DPP: Too short GAS Comeback Response"); + goto gas_fail; + } + + pos++; /* action */ + + if (*pos != s_dpp_ctx.gas_dialog_token) { + wpa_printf(MSG_DEBUG, "DPP: GAS dialog token mismatch (expected %u, got %u)", + s_dpp_ctx.gas_dialog_token, *pos); + return ESP_OK; + } + pos++; /* dialog token */ + + status_code = WPA_GET_LE16(pos); + pos += 2; + + frag_id = *pos & 0x7f; + more_frags = (*pos & 0x80) >> 7; + pos++; /* fragment id; bit7 = more fragments */ + + comeback_delay = WPA_GET_LE16(pos); + pos += 2; + + wpa_printf(MSG_DEBUG, "DPP: GAS Comeback Response: status=%u frag_id=%u " + "more_frags=%u comeback_delay=%u", + status_code, frag_id, more_frags, comeback_delay); + + if (status_code != 0 && status_code != WLAN_STATUS_QUERY_RESP_OUTSTANDING) { + wpa_printf(MSG_ERROR, "DPP: GAS Comeback Response failed status=%u", status_code); + goto gas_fail; + } + + if (comeback_delay) { + if (frag_id) { + wpa_printf(MSG_ERROR, "DPP: Invalid comeback response with " + "non-zero frag_id and comeback_delay"); + goto gas_fail; + } + if (comeback_delay > DPP_MAX_COMEBACK_DELAY_TU) { + wpa_printf(MSG_WARNING, "DPP: GAS comeback delay %u TU too large, capping to %u", + comeback_delay, DPP_MAX_COMEBACK_DELAY_TU); + comeback_delay = DPP_MAX_COMEBACK_DELAY_TU; + } + unsigned int secs = (comeback_delay * 1024) / 1000000; + unsigned int usecs = comeback_delay * 1024 - secs * 1000000; + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + if (eloop_register_timeout(secs, usecs, gas_comeback_delay_timeout, NULL, auth) != 0) { + goto gas_fail; + } + return ESP_OK; + } + + { + u8 expected_frag = s_dpp_ctx.gas_frag_id & 0x7f; + u8 previous_frag = (expected_frag - 1) & 0x7f; + + if (frag_id != expected_frag) { + if (frag_id == previous_frag) { + wpa_printf(MSG_DEBUG, "DPP: Drop frame as possible " + "retry of previous fragment"); + return ESP_OK; + } + wpa_printf(MSG_ERROR, "DPP: Unexpected frag_id %u (expected %u)", + frag_id, expected_frag); + goto gas_fail; + } + } + s_dpp_ctx.gas_frag_id = (s_dpp_ctx.gas_frag_id + 1) & 0x7f; + + left = frame_len - (size_t)(pos - frame_start); + if (left < 2 || pos[0] != WLAN_EID_ADV_PROTO || left < (size_t)(2 + pos[1])) { + wpa_printf(MSG_DEBUG, "DPP: No room for Advertisement Protocol element"); + goto gas_fail; + } + pos += 2 + pos[1]; + + left = frame_len - (size_t)(pos - frame_start); + if (left < 2) { + wpa_printf(MSG_DEBUG, "DPP: No room for GAS Response Length"); + goto gas_fail; + } + resp_len = WPA_GET_LE16(pos); + pos += 2; + + left = frame_len - (size_t)(pos - frame_start); + if (resp_len > left) { + wpa_printf(MSG_DEBUG, "DPP: Truncated GAS Comeback Response"); + goto gas_fail; + } + + if (s_dpp_ctx.gas_resp_buf && + wpabuf_len(s_dpp_ctx.gas_resp_buf) + resp_len > ESP_DPP_GAS_REASSEMBLY_MAX_LEN) { + wpa_printf(MSG_ERROR, "DPP: GAS response too long, rejecting fragments"); + ret = ESP_ERR_NO_MEM; + goto gas_fail; + } + + if (!s_dpp_ctx.gas_resp_buf) { + s_dpp_ctx.gas_resp_buf = wpabuf_alloc(resp_len + 256); + if (!s_dpp_ctx.gas_resp_buf) { + ret = ESP_ERR_NO_MEM; + goto gas_fail; + } + } + if (wpabuf_resize(&s_dpp_ctx.gas_resp_buf, resp_len) < 0) { + wpa_printf(MSG_ERROR, "DPP: No memory to store GAS fragment"); + ret = ESP_ERR_NO_MEM; + goto gas_fail; + } + wpabuf_put_data(s_dpp_ctx.gas_resp_buf, pos, resp_len); + + if (more_frags) { + if (gas_comeback_req_tx(auth) != ESP_OK) { + goto gas_fail; + } + return ESP_OK; + } + + ret = gas_process_complete_resp(auth, + wpabuf_head(s_dpp_ctx.gas_resp_buf), + wpabuf_len(s_dpp_ctx.gas_resp_buf)); + if (ret != ESP_OK) { + goto gas_fail; + } + dpp_gas_cleanup_locked(); + return ret; + +gas_fail: + dpp_gas_cleanup_locked(); + eloop_cancel_timeout(gas_query_timeout, ELOOP_ALL_CTX, ELOOP_ALL_CTX); + eloop_cancel_timeout(gas_comeback_delay_timeout, ELOOP_ALL_CTX, ELOOP_ALL_CTX); return ret; } @@ -784,12 +1313,7 @@ static void esp_dpp_rx_action(void *data, void *user_ctx) ret = esp_dpp_rx_frm(rx_param); } - } else if (public_action->action == WLAN_PA_GAS_INITIAL_RESP && - rx_param->frm_len >= 9 && - public_action->v.pa_gas_resp.type == WLAN_EID_ADV_PROTO && - public_action->v.pa_gas_resp.length == 8 && - public_action->v.pa_gas_resp.status_code == 0) { - + } else if (public_action->action == WLAN_PA_GAS_INITIAL_RESP && rx_param->frm_len >= 7) { if (!s_dpp_ctx.dpp_auth || s_dpp_ctx.dpp_auth->gas_dialog_token < 0 || public_action->v.pa_gas_resp.diag_token != @@ -799,23 +1323,45 @@ static void esp_dpp_rx_action(void *data, void *user_ctx) public_action->v.pa_gas_resp.diag_token, s_dpp_ctx.dpp_auth ? s_dpp_ctx.dpp_auth->gas_dialog_token : -1); - os_free(rx_param); - return; - } - - size_t offset = (size_t)(public_action->v.pa_gas_resp.data + - public_action->v.pa_gas_resp.length - - (u8 *)rx_param->action_frm); - - if (rx_param->frm_len < offset) { - wpa_printf(MSG_DEBUG, "DPP: Ignored too short GAS Initial Response"); goto fail_unlock; } - rx_param->vendor_data_len = rx_param->frm_len - offset; + uint16_t status_code = WPA_GET_LE16((u8 *)&public_action->v.pa_gas_resp.status_code); - wpa_printf(MSG_DEBUG, "DPP: Gas response received"); - ret = gas_query_resp_rx(rx_param); + if (status_code != 0) { + /* Non-zero status: error frame (typically 7 bytes, no Adv Proto element). + * gas_query_resp_rx handles status_code != 0 → ESP_ERR_DPP_FAILURE, + * enabling fast failure instead of waiting for a timeout. */ + if (atomic_load(&roc_in_progress)) { + listen_stop_handler(NULL, NULL); + } + wpa_printf(MSG_DEBUG, "DPP: GAS Initial Response with error status=%u", status_code); + ret = gas_query_resp_rx(rx_param); + } else if (rx_param->frm_len >= 9 && + public_action->v.pa_gas_resp.type == WLAN_EID_ADV_PROTO && + public_action->v.pa_gas_resp.length == 8) { + + size_t offset = (size_t)(public_action->v.pa_gas_resp.data - + (u8 *)rx_param->action_frm); + + if (rx_param->frm_len < offset) { + wpa_printf(MSG_DEBUG, + "DPP: GAS Initial Resp truncated (len=%u < adv_proto data offset %zu), dropping", + (unsigned)rx_param->frm_len, offset); + goto fail_unlock; + } + + if (atomic_load(&roc_in_progress)) { + listen_stop_handler(NULL, NULL); + } + + rx_param->vendor_data_len = rx_param->frm_len - offset; + wpa_printf(MSG_DEBUG, "DPP: Gas response received"); + ret = gas_query_resp_rx(rx_param); + } + } else if (public_action->action == WLAN_PA_GAS_COMEBACK_RESP && + rx_param->frm_len >= 9) { + ret = gas_comeback_resp_rx(rx_param); } fail_unlock: @@ -829,6 +1375,30 @@ fail_unlock: } return; } + + /* On successful config receipt, clean up based on AKM type: + * - Legacy AKM (PSK/SAE): DPP's job is done. The app has the SSID+password + * and will call esp_wifi_connect() directly. Stop DPP to free auth state + * and prevent stale timers from firing. + * - DPP AKM (Connector): The app still needs to call + * esp_dpp_start_net_intro_protocol(), which requires auth->conf_obj and + * auth->net_access_key. Keep auth alive but cancel GAS timers. */ + if (s_dpp_ctx.dpp_auth && s_dpp_ctx.dpp_auth->num_conf_obj > 0) { + bool has_dpp_akm = false; + for (size_t i = 0; i < s_dpp_ctx.dpp_auth->num_conf_obj; i++) { + if (dpp_akm_dpp(s_dpp_ctx.dpp_auth->conf_obj[i].akm)) { + has_dpp_akm = true; + break; + } + } + + if (!has_dpp_akm) { + dpp_stop_internal(); + } else { + /* Cancel stale GAS timers; auth is still needed for net intro */ + dpp_cancel_auth_gas_eloop_timeouts(); + } + } dpp_api_unlock(); } @@ -867,7 +1437,7 @@ static void dpp_listen_next_channel(void *data, void *user_ctx) ret = esp_wifi_remain_on_channel(&req); if (ret != ESP_OK) { wpa_printf(MSG_ERROR, "Failed ROC. error : 0x%x", ret); - dpp_abort_failure_locked(ret); + dpp_abort_failure_locked(ESP_ERR_DPP_FAILURE); return; } if (s_dpp_event_group) { @@ -1001,6 +1571,10 @@ static int esp_dpp_deinit(void *data, void *user_ctx) dpp_global_deinit(s_dpp_ctx.dpp_global); s_dpp_ctx.dpp_global = NULL; } + if (s_dpp_ctx.dpp_config_store) { + dpp_config_store_deinit(s_dpp_ctx.dpp_config_store); + s_dpp_ctx.dpp_config_store = NULL; + } if (s_dpp_ctx.dpp_auth) { dpp_auth_deinit(s_dpp_ctx.dpp_auth); s_dpp_ctx.dpp_auth = NULL; @@ -1086,6 +1660,8 @@ static void esp_dpp_auth_resp_retry(void *eloop_ctx, void *timeout_ctx) static void esp_dpp_peer_disc_retry(void *eloop_ctx, void *timeout_ctx) { struct dpp_authentication *auth; + struct dpp_config_store *config_store; + uint8_t ap_bssid[ETH_ALEN]; unsigned int max_tries = 5; /* Prerequisite confirmed via lifecycle (timers cancelled on deinit), lock acquisition will not fail. */ @@ -1097,7 +1673,8 @@ static void esp_dpp_peer_disc_retry(void *eloop_ctx, void *timeout_ctx) } auth = s_dpp_ctx.dpp_auth; - if (!auth) { + config_store = s_dpp_ctx.dpp_config_store; + if (!auth || !config_store) { dpp_api_unlock(); return; } @@ -1109,19 +1686,23 @@ static void esp_dpp_peer_disc_retry(void *eloop_ctx, void *timeout_ctx) return; } + /* auth->peer_mac_addr is the DPP Configurator's source MAC from the Auth + * Request and may differ from the AP BSSID. Use the BSSID saved on the + * original call to esp_dpp_start_net_intro_protocol(). Copy it locally + * because esp_dpp_start_net_intro_protocol_internal() memcpys its + * argument back into config_store->peer_mac_addr. */ + os_memcpy(ap_bssid, config_store->peer_mac_addr, ETH_ALEN); + wpa_printf(MSG_INFO, "DPP: Retransmitting Peer Discovery Request frame"); - if (esp_dpp_start_net_intro_protocol_internal(auth->peer_mac_addr) != ESP_OK) { + if (esp_dpp_start_net_intro_protocol_internal(ap_bssid) != ESP_OK) { dpp_abort_failure_locked(ESP_ERR_DPP_TX_FAILURE); return; } /* Safety net: if TX status is never delivered, this timeout ensures we don't * hang forever. tx_status_eloop_handler will cancel and re-register with the - * correct deadline when the TX status arrives. - * Note: Reuse gas_query_timeout for Peer Discovery phase to maintain - * compatibility with older applications that expect ESP_ERR_DPP_CONF_TIMEOUT - * on all discovery/config failures. */ - if (eloop_register_timeout(ESP_GAS_TIMEOUT_SECS + 2, 0, gas_query_timeout, NULL, auth) < 0) { - wpa_printf(MSG_ERROR, "DPP: Failed to register gas_query_timeout for intro"); + * correct deadline when the TX status arrives. */ + if (eloop_register_timeout(ESP_GAS_TIMEOUT_SECS + 2, 0, peer_disc_timeout, NULL, auth) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to register peer_disc_timeout for intro"); dpp_abort_failure_locked(ESP_ERR_DPP_FAILURE); return; } @@ -1218,22 +1799,19 @@ static void tx_status_eloop_handler(void *eloop_ctx, void *event_data) } else if (type == DPP_TX_PEER_DISCOVERY_REQ) { if (evt->status == WIFI_ACTION_TX_FAILED) { /* failed to send Peer Discovery frame, retry */ - wpa_printf(MSG_WARNING, "DPP: failed to send Peer Discovery frame"); - eloop_cancel_timeout(gas_query_timeout, NULL, auth); + wpa_printf(MSG_WARNING, "DPP: failed to send Peer Discovery frame, retrying"); + eloop_cancel_timeout(peer_disc_timeout, NULL, auth); if (eloop_register_timeout(1, 0, esp_dpp_peer_disc_retry, NULL, NULL) < 0) { - wpa_printf(MSG_ERROR, "DPP: Failed to register peer_disc_retry"); + wpa_printf(MSG_ERROR, "DPP: Failed to register esp_dpp_peer_disc_retry"); dpp_abort_failure_locked(ESP_ERR_DPP_FAILURE); os_free(evt); return; } } else if (evt->status == WIFI_ACTION_TX_DONE) { - /* Peer discovery sent, wait for response. - * Note: Reuse gas_query_timeout for Peer Discovery phase to maintain - * compatibility with older applications that expect ESP_ERR_DPP_CONF_TIMEOUT - * on all discovery/config failures. */ - eloop_cancel_timeout(gas_query_timeout, NULL, auth); - if (eloop_register_timeout(ESP_GAS_TIMEOUT_SECS, 0, gas_query_timeout, NULL, auth) < 0) { - wpa_printf(MSG_ERROR, "DPP: Failed to register gas_query_timeout after disc TX"); + /* Peer discovery sent, wait for response. */ + eloop_cancel_timeout(peer_disc_timeout, NULL, auth); + if (eloop_register_timeout(ESP_GAS_TIMEOUT_SECS, 0, peer_disc_timeout, NULL, auth) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to register peer_disc_timeout after disc TX"); dpp_abort_failure_locked(ESP_ERR_DPP_FAILURE); os_free(evt); return; @@ -1268,6 +1846,26 @@ static void tx_status_eloop_handler(void *eloop_ctx, void *event_data) return; } } + } else if (type == DPP_TX_GAS_COMEBACK) { + if (auth->auth_success) { + if (evt->status == WIFI_ACTION_TX_FAILED) { + wpa_printf(MSG_WARNING, "DPP: Failed to send GAS Comeback Request"); + dpp_abort_failure_locked(ESP_ERR_DPP_TX_FAILURE); + os_free(evt); + return; + } else if (evt->status == WIFI_ACTION_TX_DONE) { + /* Re-arm response timeout from actual send time (more precise + * than the timeout armed at queue time in gas_comeback_req_tx). */ + eloop_cancel_timeout(gas_query_timeout, NULL, auth); + if (eloop_register_timeout(ESP_GAS_TIMEOUT_SECS, 0, + gas_query_timeout, NULL, auth) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to register gas_query_timeout after comeback TX"); + dpp_abort_failure_locked(ESP_ERR_DPP_FAILURE); + os_free(evt); + return; + } + } + } } os_free(evt); dpp_api_unlock(); @@ -1317,14 +1915,12 @@ static void roc_status_eloop_handler(void *eloop_ctx, void *event_data) if (eloop_register_timeout(0, 0, dpp_listen_next_channel, NULL, NULL) < 0) { wpa_printf(MSG_ERROR, "DPP: Failed to register listen_next_channel after ROC; listen may stall"); - dpp_stop_internal(); atomic_store(&roc_in_progress, false); if (eg) { os_event_group_set_bits(eg, DPP_ROC_EVENT_HANDLED); } os_free(evt); - dpp_api_unlock(); - dpp_post_dpp_failed_event(ESP_ERR_NO_MEM); + dpp_abort_failure_locked(ESP_ERR_NO_MEM); return; } } @@ -1492,12 +2088,13 @@ esp_supp_dpp_bootstrap_gen(const char *chan_list, esp_supp_dpp_bootstrap_t type, } esp_err_t ret = ESP_OK; + char *uri_chan_list = NULL; + char *command = NULL; struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params; - char *uri_chan_list = esp_dpp_parse_chan_list(chan_list); + uri_chan_list = esp_dpp_parse_chan_list(chan_list); - wpa_printf(MSG_DEBUG, "DPP: Starting bootstrap genration"); - if (!uri_chan_list) { - wpa_printf(MSG_ERROR, "Invalid Channel list - %s", chan_list ? chan_list : "(null)"); + if (!uri_chan_list || params->num_chan > ESP_DPP_MAX_CHAN_COUNT || params->num_chan == 0) { + wpa_printf(MSG_ERROR, "DPP: Invalid channel list - %s", chan_list ? chan_list : "(null)"); s_dpp_ctx.bootstrap_done = false; ret = ESP_ERR_DPP_INVALID_LIST; goto fail; @@ -1509,28 +2106,30 @@ esp_supp_dpp_bootstrap_gen(const char *chan_list, esp_supp_dpp_bootstrap_t type, goto fail; } - params->type = type; - esp_wifi_get_mac(WIFI_IF_STA, params->mac); - size_t info_len = info ? os_strlen(info) : 0; - size_t command_len = os_strlen("type=qrcode mac=") + 17 + os_strlen(uri_chan_list) + - (key ? os_strlen("key=") + os_strlen(key) : 0) + + (key ? os_strlen(" key=") + os_strlen(key) : 0) + (info_len ? os_strlen(" info=") + info_len : 0) + 1; - char *command = os_zalloc(command_len); + command = os_zalloc(command_len); if (!command) { wpa_printf(MSG_ERROR, "DPP: Failed to allocate memory for bootstrap command"); ret = ESP_ERR_NO_MEM; goto fail; } + params->type = type; + esp_wifi_get_mac(WIFI_IF_STA, params->mac); + os_snprintf(command, command_len, "type=qrcode mac=" MACSTR "%s%s%s%s%s", MAC2STR(params->mac), uri_chan_list, - key ? "key=" : "", key ? key : "", + key ? " key=" : "", key ? key : "", info_len ? " info=" : "", info_len ? info : ""); + os_free(uri_chan_list); + uri_chan_list = NULL; + if (eloop_register_timeout(0, 0, esp_dpp_bootstrap_gen, command, NULL) < 0) { wpa_printf(MSG_ERROR, "DPP: Failed to register bootstrap_gen timeout"); os_free(command); @@ -1555,10 +2154,10 @@ static void dpp_listen_start(void *ctx, void *data) { /* Prerequisite confirmed by caller, lock acquisition will not fail. */ dpp_api_lock(); - s_dpp_ctx.dpp_listen_ongoing = true; - dpp_listen_next_channel(NULL, NULL); dpp_api_unlock(); + + dpp_listen_next_channel(NULL, NULL); } static void dpp_cancel_auth_gas_eloop_timeouts(void) @@ -1567,7 +2166,9 @@ static void dpp_cancel_auth_gas_eloop_timeouts(void) eloop_cancel_timeout(esp_dpp_auth_conf_wait_timeout, NULL, NULL); eloop_cancel_timeout(esp_dpp_auth_resp_retry, NULL, NULL); eloop_cancel_timeout(gas_query_timeout, ELOOP_ALL_CTX, ELOOP_ALL_CTX); + eloop_cancel_timeout(gas_comeback_delay_timeout, ELOOP_ALL_CTX, ELOOP_ALL_CTX); eloop_cancel_timeout(esp_dpp_peer_disc_retry, NULL, NULL); + eloop_cancel_timeout(peer_disc_timeout, ELOOP_ALL_CTX, ELOOP_ALL_CTX); eloop_cancel_timeout(esp_dpp_gas_query_req_retry, NULL, NULL); } @@ -1714,6 +2315,13 @@ static int esp_dpp_init(void *eloop_data, void *user_ctx) goto init_fail; } + s_dpp_ctx.dpp_config_store = dpp_config_store_init(); + if (!s_dpp_ctx.dpp_config_store) { + wpa_printf(MSG_ERROR, "DPP: failed to allocate memory for config store"); + ret = ESP_ERR_NO_MEM; + goto init_fail; + } + ret = esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_ACTION_TX_STATUS, &tx_status_handler, NULL); if (ret != ESP_OK) { @@ -1735,14 +2343,21 @@ static int esp_dpp_init(void *eloop_data, void *user_ctx) return ESP_OK; init_fail: + atomic_store(&dpp_shutting_down, true); + dpp_api_unlock(); esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_ACTION_TX_STATUS, &tx_status_handler); esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_ROC_DONE, &roc_status_handler); + dpp_api_lock(); if (s_dpp_ctx.dpp_global) { dpp_global_deinit(s_dpp_ctx.dpp_global); s_dpp_ctx.dpp_global = NULL; } + if (s_dpp_ctx.dpp_config_store) { + dpp_config_store_deinit(s_dpp_ctx.dpp_config_store); + s_dpp_ctx.dpp_config_store = NULL; + } dpp_api_unlock(); return ret; @@ -1800,32 +2415,30 @@ esp_err_t esp_supp_dpp_init(void) static esp_err_t esp_dpp_start_net_intro_protocol_internal(uint8_t *bssid) { - struct dpp_authentication *auth = s_dpp_ctx.dpp_auth; + struct dpp_conf *config; + struct dpp_config_store *config_store; struct wpabuf *buf = NULL; - int ret = ESP_OK; + esp_err_t ret = ESP_OK; + uint8_t curr_chan; - if (!auth) { + config = esp_dpp_config_store_get_selected_entry(); + if (!config) { + wpa_printf(MSG_ERROR, "DPP: No connection configuration set"); return ESP_ERR_INVALID_STATE; } - if (auth->num_conf_obj == 0) { - return ESP_ERR_INVALID_STATE; - } - - /* At the moment the architecture only supports one DPP configuration. - * We attempt network introduction with the first configuration object only. */ - os_memcpy(auth->peer_mac_addr, bssid, ETH_ALEN); - buf = dpp_build_peer_disc_req(auth, &auth->conf_obj[0]); + config_store = s_dpp_ctx.dpp_config_store; + os_memcpy(config_store->peer_mac_addr, bssid, ETH_ALEN); + curr_chan = config->curr_chan; + buf = dpp_build_peer_disc_req(config_store, config); if (!buf) { - return ESP_ERR_NO_MEM; + return ESP_ERR_DPP_FAILURE; } - if (esp_dpp_send_action_frame(bssid, wpabuf_head(buf), wpabuf_len(buf), - auth->curr_chan, 1000 + OFFCHAN_TX_WAIT_TIME, - DPP_TX_PEER_DISCOVERY_REQ) != ESP_OK) { - ret = ESP_FAIL; - } + ret = esp_dpp_send_action_frame(bssid, wpabuf_head(buf), wpabuf_len(buf), + curr_chan, 1000 + OFFCHAN_TX_WAIT_TIME, + DPP_TX_PEER_DISCOVERY_REQ); wpabuf_free(buf); @@ -1852,21 +2465,23 @@ esp_err_t esp_dpp_start_net_intro_protocol(uint8_t *bssid) return ESP_ERR_INVALID_STATE; } - s_dpp_ctx.gas_query_tries = 0; - /* Ensure no stale Auth, GAS, or Discovery timers from a previous attempt - * are running. If they fire, they would abort the new attempt prematurely. */ - esp_dpp_cancel_timeouts(); + * are running. If they fire, they would abort the new attempt prematurely. + * Use the narrower auth/GAS/disc cancel; full esp_dpp_cancel_timeouts() + * is reserved for deinit. */ + dpp_cancel_auth_gas_eloop_timeouts(); + s_dpp_ctx.gas_query_tries = 0; wpa_printf(MSG_INFO, "DPP: Starting Network Introduction to " MACSTR, MAC2STR(bssid)); ret = esp_dpp_start_net_intro_protocol_internal(bssid); if (ret != ESP_OK) { - if (ret == ESP_ERR_INVALID_STATE) { - dpp_api_unlock(); - } else { - dpp_abort_failure_locked(ret); - } + /* Normalize generic ESP_FAIL or ESP_ERR_INVALID_STATE to + * a DPP-specific failure so the application-facing event reason + * is meaningful and consistent. */ + esp_err_t reason = (ret == ESP_FAIL) ? ESP_ERR_DPP_TX_FAILURE : + (ret == ESP_ERR_INVALID_STATE) ? ESP_ERR_DPP_FAILURE : ret; + dpp_abort_failure_locked(reason); return ret; } @@ -1919,6 +2534,179 @@ esp_err_t esp_supp_dpp_deinit(void) return ESP_OK; } +static bool esp_dpp_stored_conf_matches_row(struct dpp_config_store *dc, + const esp_dpp_config_data_t *row) +{ + struct dpp_conf *c; + enum dpp_akm want; + + if (!dc || !row) { + return false; + } + + c = dc->conf; + want = (enum dpp_akm) row->akm; + if (!c || !dpp_akm_dpp(c->akm) || c->akm != want || c->curr_chan != row->curr_chan) { + return false; + } + if (row->connector_len > 0) { + size_t cfg_clen; + size_t c_clen; + + cfg_clen = row->connector_len; + if (cfg_clen >= ESP_DPP_MAX_CONNECTOR_LEN) { + return false; + } + if (!c->connector) { + return false; + } + c_clen = os_strlen(c->connector); + if (c_clen != cfg_clen || + os_memcmp(c->connector, row->connector, cfg_clen) != 0) { + return false; + } + } else if (c->connector) { + return false; + } + + return true; +} + +static esp_err_t esp_dpp_conf_alloc_from_config_data(const esp_dpp_config_data_t *config, + struct dpp_conf **out_conf) +{ + struct dpp_conf *new_conf = NULL; + + *out_conf = NULL; + + if (!config) { + return ESP_ERR_INVALID_ARG; + } + + if (!dpp_akm_dpp((enum dpp_akm) config->akm)) { + return ESP_ERR_INVALID_ARG; + } + + if (config->connector_len == 0 || config->net_access_key_len == 0 || + config->c_sign_key_len == 0) { + wpa_printf(MSG_ERROR, "DPP: Incomplete DPP configuration row"); + return ESP_ERR_INVALID_ARG; + } + + if (config->net_access_key_len > ESP_DPP_MAX_KEY_LEN || + config->c_sign_key_len > ESP_DPP_MAX_KEY_LEN) { + wpa_printf(MSG_ERROR, "DPP: Invalid key length in config"); + return ESP_ERR_INVALID_ARG; + } + + new_conf = os_zalloc(sizeof(struct dpp_conf)); + if (!new_conf) { + return ESP_ERR_NO_MEM; + } + + if (config->connector_len > 0) { + size_t clen; + + clen = config->connector_len; + if (clen >= ESP_DPP_MAX_CONNECTOR_LEN) { + wpa_printf(MSG_ERROR, "DPP: connector_len out of range"); + dpp_clear_confs(new_conf); + return ESP_ERR_INVALID_ARG; + } + + new_conf->connector = os_malloc(clen + 1); + if (!new_conf->connector) { + goto fail; + } + os_memcpy(new_conf->connector, config->connector, clen); + new_conf->connector[clen] = '\0'; + } + if (config->net_access_key_len) { + new_conf->net_access_key = wpabuf_alloc_copy(config->net_access_key, config->net_access_key_len); + if (!new_conf->net_access_key) { + goto fail; + } + new_conf->net_access_key_expiry = config->net_access_key_expiry; + } + if (config->c_sign_key_len) { + new_conf->c_sign_key = wpabuf_alloc_copy(config->c_sign_key, config->c_sign_key_len); + if (!new_conf->c_sign_key) { + goto fail; + } + new_conf->dpp_csign_len = config->c_sign_key_len; + } + new_conf->curr_chan = config->curr_chan; + new_conf->akm = (enum dpp_akm) config->akm; + + *out_conf = new_conf; + return ESP_OK; + +fail: + dpp_clear_confs(new_conf); + return ESP_ERR_NO_MEM; +} + +static struct dpp_conf *esp_dpp_config_store_get_selected_entry(void) +{ + struct dpp_config_store *dc = s_dpp_ctx.dpp_config_store; + + if (!dc) { + return NULL; + } + return dc->conf; +} + +esp_err_t esp_supp_dpp_set_config(const esp_dpp_config_data_t *config) +{ + struct dpp_conf *new_conf = NULL; + struct dpp_config_store *dc; + esp_err_t err; + + err = dpp_api_lock(); + if (err != ESP_OK) { + return err; + } + + if (!atomic_load(&s_dpp_init_done)) { + dpp_api_unlock(); + return ESP_ERR_INVALID_STATE; + } + + dc = s_dpp_ctx.dpp_config_store; + + if (!config) { + if (dc && dc->conf) { + dpp_clear_confs(dc->conf); + dc->conf = NULL; + } + dpp_api_unlock(); + return ESP_OK; + } + + if (!dc) { + dpp_api_unlock(); + return ESP_ERR_INVALID_STATE; + } + + if (esp_dpp_stored_conf_matches_row(dc, config)) { + dpp_api_unlock(); + return ESP_OK; + } + + err = esp_dpp_conf_alloc_from_config_data(config, &new_conf); + if (err != ESP_OK) { + dpp_api_unlock(); + return err; + } + + dpp_clear_confs(dc->conf); + dc->conf = new_conf; + + dpp_api_unlock(); + + return ESP_OK; +} + esp_err_t esp_supp_dpp_common_init(void) { if (!s_dpp_api_lock) { diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_dpp_i.h b/components/wpa_supplicant/esp_supplicant/src/esp_dpp_i.h index 5bea0d6ac4a..c80b60238ad 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_dpp_i.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_dpp_i.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,10 +12,10 @@ #include "utils/common.h" #include "common/dpp.h" #include "esp_dpp.h" -#include "esp_wifi_driver.h" #define ESP_DPP_AUTH_TIMEOUT_SECS 2 #define ESP_GAS_TIMEOUT_SECS 2 +#define ESP_DPP_GAS_REASSEMBLY_MAX_LEN 4096 #define ESP_DPP_PMK_CACHE_DEFAULT_TIMEOUT (86400 * 7) /*!< 7 days */ #define BOOTSTRAP_ROC_WAIT_TIME 500 @@ -34,24 +34,32 @@ enum dpp_tx_frame_type { DPP_TX_AUTHENTICATION_CONF, DPP_TX_PEER_DISCOVERY_REQ, DPP_TX_GAS_CONFIG_REQ, + DPP_TX_GAS_COMEBACK, }; struct esp_dpp_context_t { struct dpp_bootstrap_params_t bootstrap_params; struct dpp_authentication *dpp_auth; + int gas_dialog_token; struct dpp_global *dpp_global; - wifi_config_t wifi_cfg; + int id; + bool dpp_deinit_pending; /* True while deinit is queued so init is rejected until it finishes */ + bool bootstrap_done; + bool dpp_listen_ongoing; + struct dpp_config_store *dpp_config_store; + /* Fragmented GAS Configuration Response */ + struct wpabuf *gas_resp_buf; /* Reassembled query response data */ + uint8_t gas_frag_id; /* Expected next fragment id */ + bool gas_wait_comeback; /* Waiting for GAS Comeback Response */ + /* Retry counter for GAS query and peer discovery TX */ + unsigned int gas_query_tries; + /* Last off-channel TX for status matching */ struct { uint8_t op_id; enum dpp_tx_frame_type type; } pending_tx_op; bool pending_tx_op_in_progress; - unsigned int gas_query_tries; unsigned int listen_chan_idx; - int id; - bool dpp_deinit_pending; - bool bootstrap_done; - bool dpp_listen_ongoing; }; #ifdef CONFIG_TESTING_OPTIONS @@ -59,12 +67,17 @@ int dpp_test_gen_invalid_key(struct wpabuf *msg, const struct dpp_curve_params * char * dpp_corrupt_connector_signature(const char *connector); #endif /* CONFIG_TESTING_OPTIONS */ -#ifdef CONFIG_ESP_WIFI_DPP_SUPPORT +#ifdef CONFIG_DPP bool is_dpp_enabled(void); +esp_err_t esp_supp_dpp_common_init(void); #else static inline bool is_dpp_enabled(void) { return false; } +static inline esp_err_t esp_supp_dpp_common_init(void) +{ + return ESP_OK; +} #endif #endif /* ESP_DPP_I_H */ diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c b/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c index cf222834d30..45e7d763f65 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c @@ -551,20 +551,12 @@ int esp_supplicant_init(void) esp_wifi_register_owe_cb(wpa_cb); #endif /* CONFIG_OWE_STA */ - if (eloop_init() != 0) { - wpa_printf(MSG_ERROR, "Failed to initialize eloop"); - os_free(wpa_cb); - wpa_cb = NULL; - return ESP_FAIL; - } - + eloop_init(); ret = esp_supplicant_common_init(wpa_cb); if (ret != 0) { - /* - * Note: We don't need to explicitly call eloop_destroy() here because - * returning an error causes the caller (esp_wifi_init) to trigger the - * deinit path, which invokes esp_supplicant_deinit() and frees the eloop. + /* esp_wifi_init() propagates this error to wifi_deinit_internal() which calls + * esp_supplicant_deinit(); that path runs eloop_destroy() for eloop cleanup. */ os_free(wpa_cb); wpa_cb = NULL; diff --git a/components/wpa_supplicant/src/common/dpp.c b/components/wpa_supplicant/src/common/dpp.c index b844b1f9ea1..6882c439264 100644 --- a/components/wpa_supplicant/src/common/dpp.c +++ b/components/wpa_supplicant/src/common/dpp.c @@ -98,17 +98,9 @@ struct wpabuf * gas_build_initial_req(u8 dialog_token, size_t size) size); } -void dpp_debug_print_point(const char *title, struct crypto_ec *e, - const struct crypto_ec_point *point) +struct wpabuf * gas_build_comeback_req(u8 dialog_token) { - u8 x[64], y[64]; - - if (crypto_ec_point_to_bin(e, point, x, y) < 0) { - wpa_printf(MSG_ERROR, "Failed to get coordinates"); - return; - } - - wpa_printf(MSG_DEBUG, "%s (%s,%s)", title, x, y); + return gas_build_req(WLAN_PA_GAS_COMEBACK_REQ, dialog_token, 0); } static void dpp_auth_fail(struct dpp_authentication *auth, const char *txt) @@ -495,7 +487,7 @@ static struct wpabuf * dpp_auth_build_req(struct dpp_authentication *auth, /* Build DPP Authentication Request frame attributes */ attr_len = 2 * (4 + SHA256_MAC_LEN) + 4 + (pi ? wpabuf_len(pi) : 0) + - 4 + sizeof(wrapped_data); + 4 + sizeof(wrapped_data) + 5; if (neg_freq > 0) attr_len += 4 + 2; #ifdef CONFIG_TESTING_OPTIONS @@ -532,6 +524,11 @@ static struct wpabuf * dpp_auth_build_req(struct dpp_authentication *auth, wpabuf_put_u8(msg, channel); } + /* Protocol version: advertise v2 */ + wpabuf_put_le16(msg, DPP_ATTR_PROTOCOL_VERSION); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, 2); + #ifdef CONFIG_TESTING_OPTIONS if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_AUTH_REQ) { wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data"); @@ -658,6 +655,9 @@ static struct wpabuf * dpp_auth_build_resp(struct dpp_authentication *auth, /* Build DPP Authentication Response frame attributes */ attr_len = 4 + 1 + 2 * (4 + SHA256_MAC_LEN) + 4 + (pr ? wpabuf_len(pr) : 0) + 4 + sizeof(wrapped_data); + /* Protocol Version attribute is added below when peer_version >= 2 */ + if (auth->peer_version >= 2) + attr_len += 5; #ifdef CONFIG_TESTING_OPTIONS if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_AUTH_RESP) attr_len += 5; @@ -685,6 +685,13 @@ static struct wpabuf * dpp_auth_build_resp(struct dpp_authentication *auth, wpabuf_put_buf(msg, pr); } + /* Protocol version for v2 peer */ + if (auth->peer_version >= 2) { + wpabuf_put_le16(msg, DPP_ATTR_PROTOCOL_VERSION); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, 2); + } + attr_end = wpabuf_put(msg, 0); #ifdef CONFIG_TESTING_OPTIONS @@ -803,30 +810,97 @@ skip_wrapped_data: return msg; } -struct wpabuf * dpp_build_peer_disc_req(struct dpp_authentication *auth, struct dpp_config_obj *conf) +struct json_token * dpp_parse_own_connector(const char *own_connector); + +static u8 dpp_get_connector_version(const char *connector) +{ + struct json_token *root, *token; + u8 version = 1; + + root = dpp_parse_own_connector(connector); + if (!root) + return 1; + + token = json_get_member(root, "version"); + if (!token) + token = json_get_member(root, "v"); + + if (token && token->type == JSON_NUMBER) + version = token->number; + + json_free(root); + return version; +} + +void dpp_clear_confs(struct dpp_conf *conf) +{ + if (!conf) + return; + if (conf->connector) + bin_clear_free(conf->connector, os_strlen(conf->connector)); + wpabuf_clear_free(conf->c_sign_key); + wpabuf_clear_free(conf->net_access_key); + bin_clear_free(conf, sizeof(*conf)); +} + +void dpp_config_store_deinit(struct dpp_config_store *dc) +{ + if (!dc) + return; + dpp_clear_confs(dc->conf); + dc->conf = NULL; + bin_clear_free(dc, sizeof(*dc)); +} + +struct dpp_config_store * dpp_config_store_init(void) +{ + struct dpp_config_store *dc; + + dc = os_zalloc(sizeof(*dc)); + if (!dc) + return NULL; + return dc; +} + +struct wpabuf * dpp_build_peer_disc_req(struct dpp_config_store *dc, struct dpp_conf *conf) { struct wpabuf *msg; size_t len; struct os_time now; + int version; - if (!conf || !conf->connector || !auth || !auth->net_access_key || !conf->c_sign_key) { - wpa_printf(MSG_ERROR, "missing %s", !conf->connector ? "Connector" : !auth->net_access_key ? "netAccessKey" : "C-sign-key"); + if (!dc) { + wpa_printf(MSG_ERROR, "missing DPP config store"); + return NULL; + } + if (!conf) { + wpa_printf(MSG_ERROR, "missing DPP config"); + return NULL; + } + if (!conf->connector || !conf->net_access_key || !conf->c_sign_key) { + wpa_printf(MSG_ERROR, "missing %s", + !conf->connector ? "Connector" : + !conf->net_access_key ? "netAccessKey" : "C-sign-key"); return NULL; } os_get_time(&now); - if (auth->net_access_key_expiry && - (os_time_t) auth->net_access_key_expiry < now.sec) { + if (conf->net_access_key_expiry && + (os_time_t) conf->net_access_key_expiry < now.sec) { wpa_printf(MSG_ERROR, "netAccessKey expired"); return NULL; } + version = dpp_get_connector_version(conf->connector); + wpa_printf(MSG_DEBUG, "DPP: Starting network introduction protocol to derive PMKSA for " - MACSTR, MAC2STR(auth->peer_mac_addr)); + MACSTR, MAC2STR(dc->peer_mac_addr)); len = TRANSACTION_ID_ATTR_SET_LEN + CONNECTOR_ATTR_SET_LEN + os_strlen(conf->connector); + if (version >= 2) + len += 5; msg = dpp_alloc_msg(DPP_PA_PEER_DISCOVERY_REQ, len); if (!msg) { return NULL; @@ -852,6 +926,15 @@ struct wpabuf * dpp_build_peer_disc_req(struct dpp_authentication *auth, struct #ifdef CONFIG_TESTING_OPTIONS skip_trans_id: +#endif /* CONFIG_TESTING_OPTIONS */ + + if (version >= 2) { + wpabuf_put_le16(msg, DPP_ATTR_PROTOCOL_VERSION); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, version); + } + +#ifdef CONFIG_TESTING_OPTIONS if (dpp_test == DPP_TEST_NO_CONNECTOR_PEER_DISC_REQ) { wpa_printf(MSG_INFO, "DPP: TESTING - no Connector"); goto skip_connector; @@ -1736,11 +1819,13 @@ dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual, const u8 *i_nonce; const u8 *i_capab; const u8 *i_bootstrap; + const u8 *version; u16 wrapped_data_len; u16 i_proto_len; u16 i_nonce_len; u16 i_capab_len; u16 i_bootstrap_len; + u16 version_len; struct dpp_authentication *auth = NULL; #ifdef CONFIG_TESTING_OPTIONS u64 start_us = dpp_time_us(); @@ -1777,6 +1862,13 @@ dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual, auth->curr_chan = curr_chan; auth->peer_version = 1; /* default to the first version */ + version = dpp_get_attr(attr_start, attr_len, DPP_ATTR_PROTOCOL_VERSION, + &version_len); + if (version && version_len >= 1 && version[0] >= 2) { + auth->peer_version = version[0]; + wpa_printf(MSG_DEBUG, "DPP: Peer protocol version %u", + auth->peer_version); + } #if 0 channel = dpp_get_attr(attr_start, attr_len, DPP_ATTR_CHANNEL, @@ -2293,17 +2385,17 @@ struct wpabuf * dpp_auth_resp_rx(struct dpp_authentication *auth, const u8 *hdr, const u8 *attr_start, size_t attr_len) { - struct crypto_ec_key *pr; + struct crypto_ec_key *pr = NULL; size_t secret_len = 0; const u8 *addr[2]; size_t len[2]; u8 *unwrapped = NULL, *unwrapped2 = NULL; size_t unwrapped_len = 0, unwrapped2_len = 0; const u8 *r_bootstrap, *i_bootstrap, *wrapped_data, *status, *r_proto, - *r_nonce, *i_nonce, *r_capab, *wrapped2, *r_auth; + *r_nonce, *i_nonce, *r_capab, *wrapped2, *r_auth, *version; u16 r_bootstrap_len, i_bootstrap_len, wrapped_data_len, status_len, r_proto_len, r_nonce_len, i_nonce_len, r_capab_len, - wrapped2_len, r_auth_len; + wrapped2_len, r_auth_len, version_len; u8 r_auth2[DPP_MAX_HASH_LEN]; u8 role; @@ -2381,6 +2473,13 @@ dpp_auth_resp_rx(struct dpp_authentication *auth, const u8 *hdr, } auth->peer_version = 1; /* default to the first version */ + version = dpp_get_attr(attr_start, attr_len, DPP_ATTR_PROTOCOL_VERSION, + &version_len); + if (version && version_len >= 1 && version[0] >= 2) { + auth->peer_version = version[0]; + wpa_printf(MSG_DEBUG, "DPP: Peer protocol version %u", + auth->peer_version); + } status = dpp_get_attr(attr_start, attr_len, DPP_ATTR_STATUS, &status_len); @@ -3070,7 +3169,7 @@ static int dpp_configuration_parse(struct dpp_authentication *auth, goto fail; os_memcpy(tmp, cmd, len); tmp[len] = '\0'; - res = dpp_configuration_parse_helper(auth, cmd, 0); + res = dpp_configuration_parse_helper(auth, tmp, 0); str_clear_free(tmp); if (res) goto fail; @@ -3080,9 +3179,13 @@ static int dpp_configuration_parse(struct dpp_authentication *auth, return 0; fail: dpp_configuration_free(auth->conf_sta); + auth->conf_sta = NULL; dpp_configuration_free(auth->conf2_sta); + auth->conf2_sta = NULL; dpp_configuration_free(auth->conf_ap); + auth->conf_ap = NULL; dpp_configuration_free(auth->conf2_ap); + auth->conf2_ap = NULL; return -1; } @@ -3160,13 +3263,19 @@ void dpp_auth_deinit(struct dpp_authentication *auth) wpabuf_free(auth->resp_msg); wpabuf_free(auth->conf_req); for (i = 0; i < auth->num_conf_obj; i++) { - struct dpp_config_obj *conf = &auth->conf_obj[i]; + struct dpp_config_obj *conf = &auth->conf_obj[i]; - os_free(conf->connector); - wpabuf_free(conf->c_sign_key); + if (conf->connector) { + bin_clear_free(conf->connector, os_strlen(conf->connector)); + conf->connector = NULL; + } + wpabuf_clear_free(conf->c_sign_key); + conf->c_sign_key = NULL; } - wpabuf_free(auth->net_access_key); + wpabuf_clear_free(auth->net_access_key); + auth->net_access_key = NULL; dpp_bootstrap_info_free(auth->tmp_own_bi); + #ifdef CONFIG_TESTING_OPTIONS os_free(auth->config_obj_override); os_free(auth->discovery_override); @@ -4809,6 +4918,8 @@ static int dpp_connector_match_groups(struct json_token *own_root, } } + wpa_printf(MSG_DEBUG, "DPP: No compatible group found in peer connector"); + return 0; } diff --git a/components/wpa_supplicant/src/common/dpp.h b/components/wpa_supplicant/src/common/dpp.h index 7a2d5cfbe20..4587cea45e5 100644 --- a/components/wpa_supplicant/src/common/dpp.h +++ b/components/wpa_supplicant/src/common/dpp.h @@ -16,6 +16,7 @@ #include "crypto/sha256.h" #include "utils/includes.h" #include "utils/common.h" +#include "ieee802_11_defs.h" #include "esp_err.h" #include "esp_dpp.h" #include "crypto/crypto.h" @@ -64,17 +65,6 @@ static const u8 TRANSACTION_ID = 1; #define DPP_EVENT_INTRO "DPP-INTRO " #define DPP_EVENT_CONF_REQ_RX "DPP-CONF-REQ-RX " - -#define WLAN_ACTION_PUBLIC 4 -#define WLAN_PA_VENDOR_SPECIFIC 9 -#define OUI_WFA 0x506f9a -#define DPP_OUI_TYPE 0x1A - -#define WLAN_EID_ADV_PROTO 108 -#define WLAN_EID_VENDOR_SPECIFIC 221 - -#define WLAN_PA_GAS_INITIAL_REQ 10 - enum dpp_public_action_frame_type { DPP_PA_AUTHENTICATION_REQ = 0, DPP_PA_AUTHENTICATION_RESP = 1, @@ -234,7 +224,23 @@ struct dpp_configuration { int psk_set; }; -#define DPP_MAX_CONF_OBJ 10 +#define DPP_MAX_CONF_OBJ ESP_DPP_MAX_CONFIG_COUNT + +struct dpp_conf { + char *connector; + struct wpabuf *c_sign_key; + size_t dpp_csign_len; + struct wpabuf *net_access_key; + os_time_t net_access_key_expiry; + uint8_t curr_chan; + enum dpp_akm akm; +}; + +struct dpp_config_store { + /* Single active DPP config used for Network Introduction; applications own multi-config selection. */ + struct dpp_conf *conf; + u8 peer_mac_addr[ETH_ALEN]; +}; struct dpp_authentication { void *msg_ctx; @@ -511,9 +517,10 @@ struct wpabuf * dpp_build_conn_status_result(struct dpp_authentication *auth, enum dpp_status_error result, const u8 *ssid, size_t ssid_len, const char *channel_list); -struct wpabuf * dpp_build_peer_disc_req(struct dpp_authentication *auth, struct dpp_config_obj *conf); +struct wpabuf * dpp_build_peer_disc_req(struct dpp_config_store *dc, struct dpp_conf *conf); struct wpabuf * dpp_alloc_msg(enum dpp_public_action_frame_type type, size_t len); +struct wpabuf * gas_build_comeback_req(u8 dialog_token); const u8 * dpp_get_attr(const u8 *buf, size_t len, u16 req_id, u16 *ret_len); int dpp_check_attrs(const u8 *buf, size_t len); int dpp_key_expired(const char *timestamp, os_time_t *expiry); @@ -593,6 +600,9 @@ struct dpp_global_config { int (*process_conf_obj)(void *ctx, struct dpp_authentication *auth); }; +struct dpp_config_store * dpp_config_store_init(void); +void dpp_clear_confs(struct dpp_conf *conf); +void dpp_config_store_deinit(struct dpp_config_store *dc); struct dpp_global * dpp_global_init(struct dpp_global_config *config); void dpp_global_clear(struct dpp_global *dpp); void dpp_global_deinit(struct dpp_global *dpp); diff --git a/components/wpa_supplicant/src/common/ieee802_11_defs.h b/components/wpa_supplicant/src/common/ieee802_11_defs.h index ede713ebe30..e51c8fb1cd3 100644 --- a/components/wpa_supplicant/src/common/ieee802_11_defs.h +++ b/components/wpa_supplicant/src/common/ieee802_11_defs.h @@ -237,6 +237,7 @@ #define WLAN_EID_20_40_BSS_INTOLERANT 73 #define WLAN_EID_OVERLAPPING_BSS_SCAN_PARAMS 74 #define WLAN_EID_MMIE 76 +#define WLAN_EID_ADV_PROTO 108 #define WLAN_EID_EXT_CAPAB 127 #define WLAN_EID_MIC 140 #define WLAN_EID_VENDOR_SPECIFIC 221 diff --git a/components/wpa_supplicant/src/rsn_supp/wpa.c b/components/wpa_supplicant/src/rsn_supp/wpa.c index 55ad5cbcfc2..6fe0e3756f2 100644 --- a/components/wpa_supplicant/src/rsn_supp/wpa.c +++ b/components/wpa_supplicant/src/rsn_supp/wpa.c @@ -2667,6 +2667,10 @@ int wpa_set_bss(uint8_t *macddr, uint8_t *bssid, uint8_t pairwise_cipher, uint8_ use_pmk_cache = false; } + if (sm->key_mgmt == WPA_KEY_MGMT_DPP) { + use_pmk_cache = true; + } + if (os_memcmp(sm->ssid, ssid, ssid_len) == 0) { wpa_printf(MSG_DEBUG, "reassoc same ess and okc is %d", sm->okc); if (sm->okc == 1) { diff --git a/examples/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c index 8dba74049fa..de7420518a0 100644 --- a/examples/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c @@ -39,23 +39,165 @@ #define CURVE_SEC256R1_PKEY_HEX_DIGITS 64 static const char *TAG = "wifi dpp-enrollee"; -wifi_config_t s_dpp_wifi_config; -static int s_retry_num = 0; -/* Flag to track if bootstrap URI generation was successful. - * This allows the event handler to distinguish between a failure to generate - * the bootstrap (pre-bootstrap) and a failure during the DPP exchange (post-bootstrap). - */ +/* Multi-config state */ +static esp_dpp_config_data_t *s_dpp_configs = NULL; +static int s_config_count = 0; +static int s_active_config_index = 0; +static int s_last_attempted_config_index = -1; +static int s_connect_retry_count = 0; + +#define WIFI_MAX_RETRY_NUM 3 +#define DPP_AUTH_RETRY_MAX 3 + +#define IS_DPP_AKM(akm) \ + ((akm) == ESP_DPP_AKM_DPP || (akm) == ESP_DPP_AKM_SAE_DPP || (akm) == ESP_DPP_AKM_PSK_SAE_DPP) + +/* Flag to track if bootstrap URI generation was successful. */ static bool s_bootstrap_ready = false; /* FreeRTOS event group to signal when we are connected*/ static EventGroupHandle_t s_dpp_event_group; -#define DPP_CONNECTED_BIT BIT0 -#define DPP_CONNECT_FAIL_BIT BIT1 -#define DPP_AUTH_FAIL_BIT BIT2 -#define WIFI_MAX_RETRY_NUM 3 -#define DPP_MAX_RETRY_NUM WIFI_MAX_RETRY_NUM /* Can be configured by application if required */ +#define DPP_CONNECTED_BIT BIT0 +#define DPP_CONNECT_FAIL_BIT BIT1 +#define DPP_AUTH_FAIL_BIT BIT2 + +static void cleanup_dpp_configs(void) +{ + if (s_dpp_configs) { + free(s_dpp_configs); + s_dpp_configs = NULL; + } + s_config_count = 0; + s_active_config_index = 0; + s_last_attempted_config_index = -1; + s_connect_retry_count = 0; +} + + +static void dpp_enrollee_retry_dpp_auth_or_fail(void) +{ + esp_err_t err; + + if (s_connect_retry_count >= DPP_AUTH_RETRY_MAX) { + xEventGroupSetBits(s_dpp_event_group, DPP_AUTH_FAIL_BIT); + return; + } + + ESP_LOGI(TAG, "Retrying DPP Auth/Listen... (%d/%d)", + s_connect_retry_count + 1, DPP_AUTH_RETRY_MAX); + err = esp_supp_dpp_start_listen(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to restart DPP listen: %s", esp_err_to_name(err)); + xEventGroupSetBits(s_dpp_event_group, DPP_AUTH_FAIL_BIT); + return; + } + s_connect_retry_count++; +} + +static esp_err_t apply_config_and_connect(esp_dpp_config_data_t *config_row) +{ + wifi_config_t wifi_cfg = {0}; + esp_err_t err; + + if (!config_row) { + return ESP_ERR_INVALID_ARG; + } + + s_last_attempted_config_index = s_active_config_index; + + size_t ssid_len = config_row->ssid_len; + if (ssid_len > MAX_SSID_LEN) { + ESP_LOGW(TAG, "ssid_len %u exceeds MAX_SSID_LEN, clamping", + (unsigned)config_row->ssid_len); + ssid_len = MAX_SSID_LEN; + } + if (ssid_len > sizeof(wifi_cfg.sta.ssid)) { + ssid_len = sizeof(wifi_cfg.sta.ssid); + } + if (ssid_len) { + memcpy(wifi_cfg.sta.ssid, config_row->ssid, ssid_len); + } + + /* Auth threshold so a weaker same-SSID AP does not win the default RSSI sort */ + switch ((esp_dpp_akm_t)config_row->akm) { + case ESP_DPP_AKM_DPP: + wifi_cfg.sta.threshold.authmode = WIFI_AUTH_DPP; + break; + case ESP_DPP_AKM_SAE_DPP: + case ESP_DPP_AKM_SAE: + wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA3_PSK; + break; + case ESP_DPP_AKM_PSK_SAE_DPP: + case ESP_DPP_AKM_PSK_SAE: + wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_WPA3_PSK; + break; + case ESP_DPP_AKM_PSK: + default: + wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; + break; + } + + if (IS_DPP_AKM(config_row->akm)) { + err = esp_supp_dpp_set_config(config_row); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_supp_dpp_set_config failed: %s", esp_err_to_name(err)); + return err; + } + } else { + err = esp_supp_dpp_set_config(NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_supp_dpp_set_config(NULL) failed: %s", esp_err_to_name(err)); + return err; + } + } + + size_t pass_len = config_row->password_len; + if (pass_len > MAX_PASSPHRASE_LEN) { + ESP_LOGW(TAG, "password_len %u exceeds MAX_PASSPHRASE_LEN, clamping", + (unsigned)config_row->password_len); + pass_len = MAX_PASSPHRASE_LEN; + } + if (pass_len > sizeof(wifi_cfg.sta.password)) { + pass_len = sizeof(wifi_cfg.sta.password); + } + if (pass_len) { + memcpy(wifi_cfg.sta.password, config_row->password, pass_len); + } + + err = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_set_config failed: %s", esp_err_to_name(err)); + return err; + } + + err = esp_wifi_connect(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_connect failed: %s", esp_err_to_name(err)); + } + return err; +} + +static void advance_to_next_config_or_fail(void) +{ + s_active_config_index++; + if (s_active_config_index < s_config_count) { + esp_dpp_config_data_t *config_row = &s_dpp_configs[s_active_config_index]; + + s_connect_retry_count = 0; + ESP_LOGI(TAG, "Next config %d/%d - SSID len=%u: %.*s", + s_active_config_index + 1, s_config_count, + (unsigned)config_row->ssid_len, + (int)config_row->ssid_len, (const char *)config_row->ssid); + if (apply_config_and_connect(config_row) != ESP_OK) { + advance_to_next_config_or_fail(); + } + } else { + ESP_LOGE(TAG, "No more configs left"); + xEventGroupSetBits(s_dpp_event_group, DPP_CONNECT_FAIL_BIT); + } +} static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) @@ -63,22 +205,31 @@ static void event_handler(void *arg, esp_event_base_t event_base, if (event_base == WIFI_EVENT) { switch (event_id) { case WIFI_EVENT_STA_START: + cleanup_dpp_configs(); ESP_ERROR_CHECK(esp_supp_dpp_start_listen()); ESP_LOGI(TAG, "Started listening for DPP Authentication"); break; - case WIFI_EVENT_STA_DISCONNECTED: - if (s_retry_num < WIFI_MAX_RETRY_NUM) { - esp_wifi_connect(); - s_retry_num++; - ESP_LOGI(TAG, "Disconnect event, retry to connect to the AP"); - } else { - xEventGroupSetBits(s_dpp_event_group, DPP_CONNECT_FAIL_BIT); + case WIFI_EVENT_STA_DISCONNECTED: { + if (s_config_count > 0) { + if (s_connect_retry_count < WIFI_MAX_RETRY_NUM) { + ESP_LOGI(TAG, "Disconnect event, retry %d/%d to connect to the AP", + s_connect_retry_count + 1, WIFI_MAX_RETRY_NUM); + if (esp_wifi_connect() == ESP_OK) { + s_connect_retry_count++; + } else { + advance_to_next_config_or_fail(); + } + } else { + ESP_LOGD(TAG, "Retries exhausted on current config"); + advance_to_next_config_or_fail(); + } } break; + } case WIFI_EVENT_STA_CONNECTED: - ESP_LOGI(TAG, "Successfully connected to the AP ssid : %s ", s_dpp_wifi_config.sta.ssid); + ESP_LOGI(TAG, "Successfully connected to the AP"); break; - case WIFI_EVENT_DPP_URI_READY: + case WIFI_EVENT_DPP_URI_READY: { wifi_event_dpp_uri_ready_t *uri_data = event_data; if (uri_data != NULL) { esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT(); @@ -88,39 +239,67 @@ static void event_handler(void *arg, esp_event_base_t event_base, s_bootstrap_ready = true; } break; - case WIFI_EVENT_DPP_CFG_RECVD: - wifi_event_dpp_config_received_t *config = event_data; - memcpy(&s_dpp_wifi_config, &config->wifi_cfg, sizeof(s_dpp_wifi_config)); - s_retry_num = 0; - esp_wifi_set_config(WIFI_IF_STA, &s_dpp_wifi_config); - esp_wifi_connect(); - break; - case WIFI_EVENT_DPP_FAILED: - wifi_event_dpp_failed_t *dpp_failure = event_data; - if (s_bootstrap_ready && s_retry_num < DPP_MAX_RETRY_NUM) { - ESP_LOGI(TAG, "DPP Auth failed (Reason: %s), retry...", esp_err_to_name((int)dpp_failure->failure_reason)); - ESP_ERROR_CHECK(esp_supp_dpp_start_listen()); - s_retry_num++; - } else { - if (!s_bootstrap_ready) { - ESP_LOGE(TAG, "DPP Bootstrap generation failed (Reason: %s). Cannot retry listen.", - esp_err_to_name((int)dpp_failure->failure_reason)); - } else { - ESP_LOGE(TAG, "DPP Authentication failed after max retries (Reason: %s)", - esp_err_to_name((int)dpp_failure->failure_reason)); - } - xEventGroupSetBits(s_dpp_event_group, DPP_AUTH_FAIL_BIT); - } + } + case WIFI_EVENT_DPP_CFG_RECVD: { + wifi_event_dpp_config_received_t *ev = event_data; + cleanup_dpp_configs(); + if (ev->total_conf > 0) { + s_config_count = ev->total_conf; + s_dpp_configs = malloc(s_config_count * sizeof(esp_dpp_config_data_t)); + if (s_dpp_configs) { + memcpy(s_dpp_configs, ev->configs, s_config_count * sizeof(esp_dpp_config_data_t)); + s_active_config_index = 0; + s_connect_retry_count = 0; + + ESP_LOGI(TAG, "DPP Config Received: %d objects", s_config_count); + if (apply_config_and_connect(&s_dpp_configs[0]) != ESP_OK) { + /* First config failed at the application stage; try the + * remaining ones (consistent with the STA_DISCONNECTED + * retry-exhausted path) before declaring failure. */ + advance_to_next_config_or_fail(); + } + } else { + ESP_LOGE(TAG, "Failed to allocate memory for %d DPP configs", s_config_count); + s_config_count = 0; + xEventGroupSetBits(s_dpp_event_group, DPP_CONNECT_FAIL_BIT); + } + } break; + } + case WIFI_EVENT_DPP_FAILED: { + if (s_config_count == 0) { + wifi_event_dpp_failed_t *dpp_failure = event_data; + esp_err_t reason = (dpp_failure != NULL) ? (esp_err_t)dpp_failure->failure_reason : ESP_ERR_DPP_FAILURE; + + if (s_bootstrap_ready) { + ESP_LOGI(TAG, "DPP Authentication failed (%s), retrying...", esp_err_to_name(reason)); + dpp_enrollee_retry_dpp_auth_or_fail(); + } else { + ESP_LOGE(TAG, "DPP Bootstrap generation failed (%s)", esp_err_to_name(reason)); + xEventGroupSetBits(s_dpp_event_group, DPP_AUTH_FAIL_BIT); + } + } else { + /* + * DPP failure occurred after we already have a configuration list. + * This is typically a Network Introduction protocol failure. + * Trigger a disconnect to drive the retry/fallback state machine + * in the STA_DISCONNECTED handler. + */ + ESP_LOGE(TAG, "DPP post-provisioning failure; triggering retry"); + esp_wifi_disconnect(); + } + break; + } default: break; } } + if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); - s_retry_num = 0; + s_connect_retry_count = 0; xEventGroupSetBits(s_dpp_event_group, DPP_CONNECTED_BIT); } } @@ -190,17 +369,52 @@ void dpp_enrollee_init(void) /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually * happened. */ if (bits & DPP_CONNECTED_BIT) { + ESP_LOGI(TAG, "DPP Provisioning and Connection successful"); } else if (bits & DPP_CONNECT_FAIL_BIT) { - ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", - s_dpp_wifi_config.sta.ssid, s_dpp_wifi_config.sta.password); + ESP_LOGI(TAG, "Failed to connect after exhausting all configurations"); } else if (bits & DPP_AUTH_FAIL_BIT) { - ESP_LOGI(TAG, "DPP Authentication failed after %d retries", s_retry_num); + ESP_LOGI(TAG, "DPP Authentication failed"); + } else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); } + uint8_t diag_ssid_len = 0; + uint8_t diag_ssid[32]; // MAX_SSID_LEN + bool diag_password_set = false; + bool diag_have_last_config = false; + + if (bits & DPP_CONNECT_FAIL_BIT) { + int last_idx = s_last_attempted_config_index; + + if (last_idx < 0 && s_config_count > 0) { + last_idx = s_active_config_index >= s_config_count ? s_config_count - 1 : s_active_config_index; + } + if (s_dpp_configs != NULL && last_idx >= 0 && last_idx < s_config_count) { + const esp_dpp_config_data_t *row = &s_dpp_configs[last_idx]; + + diag_ssid_len = row->ssid_len; + if (diag_ssid_len > 32) { + diag_ssid_len = 32; + } + memcpy(diag_ssid, row->ssid, diag_ssid_len); + diag_password_set = row->password_len > 0; + diag_have_last_config = true; + } + } + + if (bits & DPP_CONNECT_FAIL_BIT && diag_have_last_config) { + ESP_LOGI(TAG, "Last attempted config -> SSID len=%u: %.*s, password: %s", + (unsigned)diag_ssid_len, + (int)diag_ssid_len, + (const char *)diag_ssid, + diag_password_set ? "(set)" : "(not set)"); + } + esp_supp_dpp_deinit(); s_bootstrap_ready = false; + cleanup_dpp_configs(); + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler)); ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler)); vEventGroupDelete(s_dpp_event_group);