From: Ainy Kumari Add an extended feature flag NL80211_EXT_FEATURE_EPPKE to allow a driver to indicate support for the Enhanced Privacy Protection Key Exchange (EPPKE) authentication protocol in non‑AP STA mode, as defined in "IEEE P802.11bi/D3.0, 12.16.9". In case of SME in userspace, the Authentication frame body is prepared in userspace while the driver finalizes the Authentication frame once it receives the required fields and elements. The driver indicates support for EPPKE using the extended feature flag so that userspace can initiate EPPKE authentication. When the feature flag is set, process EPPKE Authentication frames from userspace in non-AP STA mode. If the flag is not set, reject EPPKE Authentication frames. Define a new authentication type NL80211_AUTHTYPE_EPPKE for EPPKE. Add support to validate the EPPKE base AKM suite in RSNE and reject EPPKE Authentication frames if the base AKM suite is not present. Signed-off-by: Ainy Kumari Co-developed-by: Kavita Kavita Signed-off-by: Kavita Kavita --- include/linux/ieee80211.h | 1 + include/net/cfg80211.h | 55 +++++++++++++++ include/uapi/linux/nl80211.h | 7 ++ net/wireless/core.h | 2 + net/wireless/nl80211.c | 59 +++++++++++++++- net/wireless/util.c | 126 +++++++++++++++++++++++++++++++++++ 6 files changed, 248 insertions(+), 2 deletions(-) diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 9d36695e1468..b3bf98a317b2 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -1351,6 +1351,7 @@ struct ieee80211_tdls_data { #define WLAN_AUTH_FILS_SK 4 #define WLAN_AUTH_FILS_SK_PFS 5 #define WLAN_AUTH_FILS_PK 6 +#define WLAN_AUTH_EPPKE 9 #define WLAN_AUTH_LEAP 128 #define WLAN_AUTH_CHALLENGE_LEN 128 diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 899f267b7cf9..38d201d4e676 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -10376,4 +10376,59 @@ cfg80211_s1g_get_primary_sibling(struct wiphy *wiphy, return ieee80211_get_channel_khz(wiphy, sibling_1mhz_khz); } +/** + * struct rsne - RSN element (RSNE) + * @version: RSN version field (must be 1 for valid RSNE). + * @group_data_cipher_suite: group data cipher suite selector. + * @pairwise_cipher_suite_count: number of pairwise cipher suites. + * @pairwise_cipher_suite_list: list of pairwise cipher suite selectors. + * @akm_suite_count: number of AKM suites. + * @akm_suite_list: list of AKM suite selectors. + * @capabilities: RSN capabilities bitfield. + * @pmkid_count: (optional) number of PMKIDs. + * @pmkid_list: (optional) list of PMKIDs. + * @group_mgmt_cipher_suite: (optional) group management cipher suite selector. + * + * Represents the RSN element defined in "IEEE Std 802.11-2020, 9.4.2.24" + * so that cfg80211/mac80211 can parse and access its fields. + */ +struct rsne { + u16 version; + u32 group_data_cipher_suite; + u16 pairwise_cipher_suite_count; + const u8 *pairwise_cipher_suite_list; + + u16 akm_suite_count; + const u8 *akm_suite_list; + + u16 capabilities; + + u16 pmkid_count; + const u8 *pmkid_list; + + u32 group_mgmt_cipher_suite; +}; + +/** + * cfg80211_parse_rsne - Parse an RSN element (RSNE) + * @rsne: pointer to RSNE buffer. + * @elem: pointer to struct rsne to fill. + * + * Parse the RSN element as defined in "IEEE Std 802.11-2020, 9.4.2.24". + * + * Return: 0 on success, -EINVAL on failure. + */ +int cfg80211_parse_rsne(const u8 *rsne, struct rsne *elem); + +/** + * cfg80211_rsne_get_akm_list - Parse RSNE and return AKM suite list + * @rsne: pointer to RSNE buffer. + * @count: pointer to store number of AKM suites. + * + * Parse the RSN element and return a pointer to the AKM suite list. + * + * Return: pointer to list, or NULL if parsing fails or inputs are invalid. + */ +const u8 *cfg80211_rsne_get_akm_list(const u8 *rsne, u16 *count); + #endif /* __NET_CFG80211_H */ diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 8134f10e4e6c..371249a2f0b0 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -5425,6 +5425,7 @@ enum nl80211_bss_status { * @NL80211_AUTHTYPE_FILS_SK: Fast Initial Link Setup shared key * @NL80211_AUTHTYPE_FILS_SK_PFS: Fast Initial Link Setup shared key with PFS * @NL80211_AUTHTYPE_FILS_PK: Fast Initial Link Setup public key + * @NL80211_AUTHTYPE_EPPKE: Enhanced Privacy Protection Key Exchange * @__NL80211_AUTHTYPE_NUM: internal * @NL80211_AUTHTYPE_MAX: maximum valid auth algorithm * @NL80211_AUTHTYPE_AUTOMATIC: determine automatically (if necessary by @@ -5440,6 +5441,7 @@ enum nl80211_auth_type { NL80211_AUTHTYPE_FILS_SK, NL80211_AUTHTYPE_FILS_SK_PFS, NL80211_AUTHTYPE_FILS_PK, + NL80211_AUTHTYPE_EPPKE, /* keep last */ __NL80211_AUTHTYPE_NUM, @@ -6744,6 +6746,10 @@ enum nl80211_feature_flags { * @NL80211_EXT_FEATURE_BEACON_RATE_EHT: Driver supports beacon rate * configuration (AP/mesh) with EHT rates. * + * @NL80211_EXT_FEATURE_EPPKE: Driver supports Enhanced Privacy Protection + * Key Exchange (EPPKE) with user space SME (NL80211_CMD_AUTHENTICATE) + * in non-AP STA mode. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -6820,6 +6826,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_DFS_CONCURRENT, NL80211_EXT_FEATURE_SPP_AMSDU_SUPPORT, NL80211_EXT_FEATURE_BEACON_RATE_EHT, + NL80211_EXT_FEATURE_EPPKE, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/wireless/core.h b/net/wireless/core.h index 63dcf315dba7..134c8841a24d 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -579,6 +579,8 @@ int cfg80211_assoc_ml_reconf(struct cfg80211_registered_device *rdev, struct net_device *dev, struct cfg80211_ml_reconf_req *req); +bool cfg80211_is_sae_akmp(u32 akm_suite); + /** * struct cfg80211_colocated_ap - colocated AP information * diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index c961cd42a832..d7151fc5cf0e 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6470,6 +6470,10 @@ static bool nl80211_valid_auth_type(struct cfg80211_registered_device *rdev, auth_type == NL80211_AUTHTYPE_FILS_SK_PFS || auth_type == NL80211_AUTHTYPE_FILS_PK)) return false; + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_EPPKE) && + auth_type == NL80211_AUTHTYPE_EPPKE) + return false; return true; case NL80211_CMD_CONNECT: if (!(rdev->wiphy.features & NL80211_FEATURE_SAE) && @@ -6487,6 +6491,10 @@ static bool nl80211_valid_auth_type(struct cfg80211_registered_device *rdev, NL80211_EXT_FEATURE_FILS_SK_OFFLOAD) && auth_type == NL80211_AUTHTYPE_FILS_SK) return false; + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_EPPKE) && + auth_type == NL80211_AUTHTYPE_EPPKE) + return false; return true; case NL80211_CMD_START_AP: if (!wiphy_ext_feature_isset(&rdev->wiphy, @@ -11953,7 +11961,8 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) if ((auth_type == NL80211_AUTHTYPE_SAE || auth_type == NL80211_AUTHTYPE_FILS_SK || auth_type == NL80211_AUTHTYPE_FILS_SK_PFS || - auth_type == NL80211_AUTHTYPE_FILS_PK) && + auth_type == NL80211_AUTHTYPE_FILS_PK || + auth_type == NL80211_AUTHTYPE_EPPKE) && !info->attrs[NL80211_ATTR_AUTH_DATA]) return -EINVAL; @@ -11961,12 +11970,58 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) if (auth_type != NL80211_AUTHTYPE_SAE && auth_type != NL80211_AUTHTYPE_FILS_SK && auth_type != NL80211_AUTHTYPE_FILS_SK_PFS && - auth_type != NL80211_AUTHTYPE_FILS_PK) + auth_type != NL80211_AUTHTYPE_FILS_PK && + auth_type != NL80211_AUTHTYPE_EPPKE) return -EINVAL; req.auth_data = nla_data(info->attrs[NL80211_ATTR_AUTH_DATA]); req.auth_data_len = nla_len(info->attrs[NL80211_ATTR_AUTH_DATA]); } + if (auth_type == NL80211_AUTHTYPE_EPPKE) { + u16 auth_trans; + __le16 *pos; + + /* + * Validate auth_data length for Authentication + * Transaction Sequence Number and Status Code. + */ + if (req.auth_data_len <= 4) + return -EINVAL; + + pos = (__le16 *)req.auth_data; + auth_trans = le16_to_cpu(*pos); + + if (auth_trans == 1) { + const u8 *rsne, *akm_list; + u16 akm_count; + u32 akm_suite; + + rsne = cfg80211_find_ie(WLAN_EID_RSN, + req.auth_data + 4, + req.auth_data_len - 4); + + akm_list = cfg80211_rsne_get_akm_list(rsne, &akm_count); + + /* + * Validate AKMP from RSNE for EPPKE Authentication: + * EPPKE uses PASN with SAE AKMPs as Base AKMP as + * mentioned in "IEEE P802.11bi/D3.0, 12.16.9". + * Valid AKMPs: SAE (00-0F-AC:8), FT-SAE (00-0F-AC:9), + * SAE-EXT (00-0F-AC:24), FT-SAE-EXT (00-0F-AC:25). + * + * If RSNE has none or multiple Base AKMPs, reject the + * Authentication frame as mentioned in + * "IEEE Std 802.11‑2024, 12.13.3.2". + */ + if (akm_count != 1 || !akm_list) + return -EINVAL; + + akm_suite = get_unaligned_be32(akm_list); + if (!cfg80211_is_sae_akmp(akm_suite)) + return -EINVAL; + } + } + local_state_change = !!info->attrs[NL80211_ATTR_LOCAL_STATE_CHANGE]; /* diff --git a/net/wireless/util.c b/net/wireless/util.c index 27e8a2f52f04..cc09769fa25f 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -2984,3 +2984,129 @@ bool cfg80211_wdev_channel_allowed(struct wireless_dev *wdev, return false; } EXPORT_SYMBOL(cfg80211_wdev_channel_allowed); + +int cfg80211_parse_rsne(const u8 *rsne, struct rsne *elem) +{ + u16 len, cnt, field_len; + + if (!rsne) + return -EINVAL; + + memset(elem, 0, sizeof(*elem)); + + /* Version present? */ + if (rsne[1] < 2) + return -EINVAL; + + elem->version = get_unaligned_le16(rsne + 2); + if (elem->version != 1) + return -EINVAL; + + len = rsne[1] - 2; + rsne += 4; + + /* Group Data Cipher Suite present? */ + if (len >= 4) { + elem->group_data_cipher_suite = get_unaligned_be32(rsne); + rsne += 4; + len -= 4; + } else if (len > 0) { + return -EINVAL; + } + + /* Pairwise Cipher Suite Count present? */ + if (len >= 2) { + cnt = get_unaligned_le16(rsne); + field_len = 2 + cnt * 4; + + if (len < field_len) + return -EINVAL; + + elem->pairwise_cipher_suite_count = cnt; + elem->pairwise_cipher_suite_list = rsne + 2; + + rsne += field_len; + len -= field_len; + } else if (len == 1) { + return -EINVAL; + } + + /* AKM Suite Count present? */ + if (len >= 2) { + cnt = get_unaligned_le16(rsne); + field_len = 2 + cnt * 4; + + if (len < field_len) + return -EINVAL; + + elem->akm_suite_count = cnt; + elem->akm_suite_list = rsne + 2; + + rsne += field_len; + len -= field_len; + } else if (len == 1) { + return -EINVAL; + } + + /* RSN Capabilities present? */ + if (len >= 2) { + elem->capabilities = get_unaligned_le16(rsne); + rsne += 2; + len -= 2; + } + + /* PMKID Count present? */ + if (len >= 2) { + cnt = get_unaligned_le16(rsne); + field_len = 2 + cnt * 16; + + if (len < field_len) + return -EINVAL; + + elem->pmkid_count = cnt; + elem->pmkid_list = rsne + 2; + + rsne += field_len; + len -= field_len; + } + + /* Group Management Cipher Suite present? */ + if (len >= 4) { + elem->group_mgmt_cipher_suite = get_unaligned_be32(rsne); + rsne += 4; + len -= 4; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cfg80211_parse_rsne); + +const u8 *cfg80211_rsne_get_akm_list(const u8 *rsne, u16 *count) +{ + struct rsne elem; + int ret; + + if (!rsne || !count) + return NULL; + + ret = cfg80211_parse_rsne(rsne, &elem); + if (ret) + return NULL; + + *count = elem.akm_suite_count; + + return elem.akm_suite_list; +} + +bool cfg80211_is_sae_akmp(u32 akm_suite) +{ + switch (akm_suite) { + case WLAN_AKM_SUITE_SAE: + case WLAN_AKM_SUITE_FT_OVER_SAE: + case WLAN_AKM_SUITE_SAE_EXT_KEY: + case WLAN_AKM_SUITE_FT_SAE_EXT_KEY: + return true; + default: + return false; + } +} -- 2.34.1