From 354200f9dae37d07811e1b137c9c5ab39afbf66d Mon Sep 17 00:00:00 2001 From: Denis Kenzior Date: Thu, 1 Aug 2024 23:28:09 -0500 Subject: [PATCH] netdev: external auth support Certain FullMAC drivers do not expose CMD_ASSOCIATE/CMD_AUTHENTICATE, but lack the ability to fully offload SAE connections to the firmware. Such connections can still be supported on such firmware by using CMD_EXTERNAL_AUTH & CMD_FRAME. The firmware sets the NL80211_FEATURE_SAE bit (which implies support for CMD_AUTHENTICATE, but oh well), and no other offload extended features. When CMD_CONNECT is issued, the firmware sends CMD_EXTERNAL_AUTH via unicast to the owner of the connection. The connection owner is then expected to send SAE frames with the firmware using CMD_FRAME and receive authenticate frames using unicast CMD_FRAME notifications as well. Once SAE authentication completes, userspace is expected to send a final CMD_EXTERNAL_AUTH back to the kernel with the corresponding status code. On failure, a non-0 status code should be used. Note that for historical reasons, SAE AKM sent in CMD_EXTERNAL_AUTH is given in big endian order, not CPU order as is expected! --- src/netdev.c | 257 +++++++++++++++++++++++++++++++++++++++++----- src/nl80211util.c | 4 +- src/wiphy.c | 19 ++-- 3 files changed, 238 insertions(+), 42 deletions(-) diff --git a/src/netdev.c b/src/netdev.c index 1e923805..1b50a540 100644 --- a/src/netdev.c +++ b/src/netdev.c @@ -192,6 +192,7 @@ struct netdev { bool in_reassoc : 1; bool privacy : 1; bool cqm_poll_fallback : 1; + bool external_auth : 1; }; struct netdev_preauth_state { @@ -871,6 +872,7 @@ static void netdev_connect_free(struct netdev *netdev) netdev->expect_connect_failure = false; netdev->cur_rssi_low = false; netdev->privacy = false; + netdev->external_auth = false; if (netdev->connect_cmd) { l_genl_msg_unref(netdev->connect_cmd); @@ -2478,7 +2480,10 @@ static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev, switch (nhs->type) { case CONNECTION_TYPE_SOFTMAC: + break; case CONNECTION_TYPE_FULLMAC: + l_genl_msg_append_attr(msg, + NL80211_ATTR_EXTERNAL_AUTH_SUPPORT, 0, NULL); break; case CONNECTION_TYPE_SAE_OFFLOAD: l_genl_msg_append_attr(msg, NL80211_ATTR_SAE_PASSWORD, @@ -3392,6 +3397,77 @@ static void netdev_fils_tx_associate(struct iovec *fils_iov, size_t n_fils_iov, } } +static void netdev_external_auth_frame_cb(struct l_genl_msg *msg, + void *user_data) +{ + int error = l_genl_msg_get_error(msg); + + if (error < 0) + l_debug("Failed to send External Auth Frame: %s(%d)", + strerror(-error), -error); +} + +static void netdev_external_auth_sae_tx_authenticate(const uint8_t *body, + size_t body_len, void *user_data) +{ + struct netdev *netdev = user_data; + struct handshake_state *hs = netdev->handshake; + uint16_t frame_type = MPDU_MANAGEMENT_SUBTYPE_AUTHENTICATION << 4; + struct iovec iov[2]; + struct l_genl_msg *msg; + uint8_t algorithm[2] = { 0x03, 0x00 }; + + l_debug(""); + + iov[0].iov_base = &algorithm; + iov[0].iov_len = sizeof(algorithm); + iov[1].iov_base = (void *) body; + iov[1].iov_len = body_len; + + msg = nl80211_build_cmd_frame(netdev->index, frame_type, + hs->spa, hs->aa, 0, iov, 2); + + if (l_genl_family_send(nl80211, msg, netdev_external_auth_frame_cb, + netdev, NULL) > 0) + return; + + l_genl_msg_unref(msg); +} + +static void netdev_external_auth_cb(struct l_genl_msg *msg, void *user_data) +{ + int error = l_genl_msg_get_error(msg); + + if (error < 0) + l_debug("Failed to send External Auth: %s(%d)", + strerror(-error), -error); +} + +static void netdev_send_external_auth(struct netdev *netdev, + uint16_t status_code) +{ + struct handshake_state *hs = netdev->handshake; + struct l_genl_msg *msg = + nl80211_build_external_auth(netdev->index, status_code, + hs->ssid, hs->ssid_len, hs->aa); + + if (l_genl_family_send(nl80211, msg, netdev_external_auth_cb, + netdev, NULL) > 0) + return; + + l_genl_msg_unref(msg); +} + +static void netdev_external_auth_sae_tx_associate(void *user_data) +{ + struct netdev *netdev = user_data; + + l_debug(""); + + netdev_send_external_auth(netdev, MMPDU_STATUS_CODE_SUCCESS); + netdev_ensure_eapol_registered(netdev); +} + struct rtnl_data { struct netdev *netdev; uint8_t addr[ETH_ALEN]; @@ -3400,6 +3476,10 @@ struct rtnl_data { static int netdev_begin_connection(struct netdev *netdev) { + struct netdev_handshake_state *nhs = + l_container_of(netdev->handshake, + struct netdev_handshake_state, super); + if (netdev->connect_cmd) { netdev->connect_cmd_id = l_genl_family_send(nl80211, netdev->connect_cmd, @@ -3419,7 +3499,7 @@ static int netdev_begin_connection(struct netdev *netdev) */ handshake_state_set_supplicant_address(netdev->handshake, netdev->addr); - if (netdev->ap) { + if (netdev->ap && nhs->type == CONNECTION_TYPE_SOFTMAC) { if (!auth_proto_start(netdev->ap)) goto failed; @@ -3870,7 +3950,11 @@ static int netdev_handshake_state_setup_connection_type( if (softmac && wiphy_has_feature(wiphy, NL80211_FEATURE_SAE)) goto softmac; - return -EINVAL; + /* FullMAC uses EXTERNAL_AUTH and reuses this feature bit */ + if (wiphy_has_feature(wiphy, NL80211_FEATURE_SAE)) + goto fullmac; + + return -ENOTSUP; case IE_RSN_AKM_SUITE_FILS_SHA256: case IE_RSN_AKM_SUITE_FILS_SHA384: case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256: @@ -3941,40 +4025,43 @@ static void netdev_connect_common(struct netdev *netdev, goto done; } - if (nhs->type != CONNECTION_TYPE_SOFTMAC) + if (!IE_AKM_IS_SAE(hs->akm_suite) || + nhs->type == CONNECTION_TYPE_SAE_OFFLOAD) goto build_cmd_connect; - switch (hs->akm_suite) { - case IE_RSN_AKM_SUITE_SAE_SHA256: - case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256: + if (nhs->type == CONNECTION_TYPE_SOFTMAC) netdev->ap = sae_sm_new(hs, netdev_sae_tx_authenticate, - netdev_sae_tx_associate, - netdev); + netdev_sae_tx_associate, + netdev); + else + netdev->ap = + sae_sm_new(hs, netdev_external_auth_sae_tx_authenticate, + netdev_external_auth_sae_tx_associate, + netdev); - if (sae_sm_is_h2e(netdev->ap)) { - uint8_t own_rsnxe[20]; + if (sae_sm_is_h2e(netdev->ap)) { + uint8_t own_rsnxe[20]; - if (wiphy_get_rsnxe(netdev->wiphy, - own_rsnxe, sizeof(own_rsnxe))) { - set_bit(own_rsnxe + 2, IE_RSNX_SAE_H2E); - handshake_state_set_supplicant_rsnxe(hs, - own_rsnxe); - } - } - - break; - default: -build_cmd_connect: - cmd_connect = netdev_build_cmd_connect(netdev, hs, prev_bssid); - - if (!is_offload(hs) && (is_rsn || hs->settings_8021x)) { - sm = eapol_sm_new(hs); - - if (nhs->type == CONNECTION_TYPE_8021X_OFFLOAD) - eapol_sm_set_require_handshake(sm, false); + if (wiphy_get_rsnxe(netdev->wiphy, + own_rsnxe, sizeof(own_rsnxe))) { + set_bit(own_rsnxe + 2, IE_RSNX_SAE_H2E); + handshake_state_set_supplicant_rsnxe(hs, + own_rsnxe); } } + if (nhs->type == CONNECTION_TYPE_SOFTMAC) + goto done; + +build_cmd_connect: + cmd_connect = netdev_build_cmd_connect(netdev, hs, prev_bssid); + + if (!is_offload(hs) && (is_rsn || hs->settings_8021x)) { + sm = eapol_sm_new(hs); + + if (nhs->type == CONNECTION_TYPE_8021X_OFFLOAD) + eapol_sm_set_require_handshake(sm, false); + } done: netdev->connect_cmd = cmd_connect; netdev->event_filter = event_filter; @@ -4483,6 +4570,52 @@ static void netdev_qos_map_frame_event(const struct mmpdu_header *hdr, netdev_send_qos_map_set(netdev, body + 4, body_len - 4); } +static void netdev_sae_external_auth_frame_event(const struct mmpdu_header *hdr, + const void *body, size_t body_len, + int rssi, void *user_data) +{ + struct netdev *netdev = user_data; + const struct mmpdu_authentication *auth; + uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED; + int ret; + + if (!netdev->external_auth) + return; + + if (!netdev->ap) + return; + + auth = mmpdu_body(hdr); + /* + * Allows station to persist settings so it does not retry + * the higher order ECC group again + */ + if (L_CPU_TO_LE16(auth->status) == + MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP && + netdev->event_filter) + netdev->event_filter(netdev, NETDEV_EVENT_ECC_GROUP_RETRY, + NULL, netdev->user_data); + + ret = auth_proto_rx_authenticate(netdev->ap, (const void *) hdr, + mmpdu_header_len(hdr) + body_len); + + switch (ret) { + case 0: + case -EAGAIN: + return; + case -ENOMSG: + case -EBADMSG: + return; + default: + break; + } + + if (ret > 0) + status_code = (uint16_t)ret; + + netdev_send_external_auth(netdev, status_code); +} + static void netdev_preauth_cb(const uint8_t *pmk, void *user_data) { struct netdev_preauth_state *preauth = user_data; @@ -5307,6 +5440,63 @@ static void netdev_control_port_frame_event(struct l_genl_msg *msg, frame, frame_len, unencrypted); } +static void netdev_external_auth_event(struct l_genl_msg *msg, + struct netdev *netdev) +{ + const uint8_t *bssid; + struct iovec ssid; + uint32_t akm; + uint32_t action; + struct handshake_state *hs = netdev->handshake; + + if (L_WARN_ON(nl80211_parse_attrs(msg, NL80211_ATTR_AKM_SUITES, &akm, + NL80211_ATTR_EXTERNAL_AUTH_ACTION, &action, + NL80211_ATTR_BSSID, &bssid, + NL80211_ATTR_SSID, &ssid, + NL80211_ATTR_UNSPEC) < 0)) + return; + + if (!L_IN_SET(action, NL80211_EXTERNAL_AUTH_START, + NL80211_EXTERNAL_AUTH_ABORT)) + return; + + /* kernel sends SAE_SHA256 AKM in BE order for legacy reasons */ + if (!L_IN_SET(akm, CRYPTO_AKM_SAE_SHA256, CRYPTO_AKM_FT_OVER_SAE_SHA256, + L_CPU_TO_BE32(CRYPTO_AKM_SAE_SHA256))) { + l_warn("Unknown AKM: %08x", akm); + return; + } + + if (action == NL80211_EXTERNAL_AUTH_ABORT) { + iwd_notice(IWD_NOTICE_CONNECT_INFO, "External Auth Aborted"); + goto error; + } + + iwd_notice(IWD_NOTICE_CONNECT_INFO, + "External Auth to SSID: %s, bssid: "MAC, + util_ssid_to_utf8(ssid.iov_len, ssid.iov_base), + MAC_STR(bssid)); + + if (hs->ssid_len != ssid.iov_len || + memcmp(hs->ssid, ssid.iov_base, hs->ssid_len)) { + iwd_notice(IWD_NOTICE_CONNECT_INFO, "Target SSID mismatch"); + goto error; + } + + if (memcmp(hs->aa, bssid, ETH_ALEN)) { + iwd_notice(IWD_NOTICE_CONNECT_INFO, "Target BSSID mismatch"); + goto error; + } + + if (auth_proto_start(netdev->ap)) { + netdev->external_auth = true; + return; + } + +error: + netdev_send_external_auth(netdev, MMPDU_STATUS_CODE_UNSPECIFIED); +} + static void netdev_unicast_notify(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = NULL; @@ -5344,6 +5534,9 @@ static void netdev_unicast_notify(struct l_genl_msg *msg, void *user_data) case NL80211_CMD_CONTROL_PORT_FRAME: netdev_control_port_frame_event(msg, netdev); break; + case NL80211_CMD_EXTERNAL_AUTH: + netdev_external_auth_event(msg, netdev); + break; } } @@ -5518,6 +5711,7 @@ static void netdev_add_station_frame_watches(struct netdev *netdev) static const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 }; static const uint8_t auth_ft_response_prefix[] = { 0x02, 0x00 }; static const uint8_t action_qos_map_prefix[] = { 0x01, 0x04 }; + static const uint8_t auth_sae_prefix[] = { 0x03, 0x00 }; uint64_t wdev = netdev->wdev_id; /* Subscribe to Management -> Action -> RM -> Neighbor Report frames */ @@ -5545,6 +5739,13 @@ static void netdev_add_station_frame_watches(struct netdev *netdev) frame_watch_add(wdev, 0, 0x00d0, action_qos_map_prefix, sizeof(action_qos_map_prefix), netdev_qos_map_frame_event, netdev, NULL); + + if (!wiphy_supports_cmds_auth_assoc(netdev->wiphy) && + wiphy_has_feature(netdev->wiphy, NL80211_FEATURE_SAE)) + frame_watch_add(wdev, 0, 0x00b0, + auth_sae_prefix, sizeof(auth_sae_prefix), + netdev_sae_external_auth_frame_event, + netdev, NULL); } static void netdev_setup_interface(struct netdev *netdev) diff --git a/src/nl80211util.c b/src/nl80211util.c index fcf70b9f..7590f90c 100644 --- a/src/nl80211util.c +++ b/src/nl80211util.c @@ -648,7 +648,9 @@ struct l_genl_msg *nl80211_build_cmd_frame(uint32_t ifindex, msg = l_genl_msg_new_sized(NL80211_CMD_FRAME, 128 + 512); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &ifindex); - l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &freq); + + if (freq) + l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &freq); l_genl_msg_append_attrv(msg, NL80211_ATTR_FRAME, iovs, iov_len + 1); return msg; diff --git a/src/wiphy.c b/src/wiphy.c index cdaa89ad..06f72ef2 100644 --- a/src/wiphy.c +++ b/src/wiphy.c @@ -230,29 +230,22 @@ static bool wiphy_can_connect_sae(struct wiphy *wiphy) * cards the entire SAE protocol as well as the subsequent 4-way * handshake are all done in the driver/firmware (fullMAC). * - * 3. TODO: Cards which allow SAE in userspace via CMD_EXTERNAL_AUTH. + * 3. Cards which allow SAE in userspace via CMD_EXTERNAL_AUTH. * These cards do not support AUTH/ASSOC commands but do implement * CMD_EXTERNAL_AUTH which is supposed to allow userspace to - * generate Authenticate frames as it would for case (1). As it - * stands today only one driver actually uses CMD_EXTERNAL_AUTH and - * for now IWD will not allow connections to SAE networks using this - * mechanism. + * generate Authenticate frames as it would for case (1). */ - if (wiphy_has_feature(wiphy, NL80211_FEATURE_SAE)) { /* Case (1) */ if (wiphy->support_cmds_auth_assoc) return true; - /* - * Case (3) - * - * TODO: No support for CMD_EXTERNAL_AUTH yet. - */ - l_warn("SAE unsupported: %s needs CMD_EXTERNAL_AUTH for SAE", + /* Case 3 */ + iwd_notice(IWD_NOTICE_CONNECT_INFO, + "FullMAC driver: %s using SAE. Expect EXTERNAL_AUTH", wiphy->driver_str); - return false; + return true; } /* Case (2) */