From cd3f82ce8c2f4eff92d57007f75f28f8b98a685b Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Tue, 27 Sep 2022 12:47:21 -0700 Subject: [PATCH] ft: netdev: prep for FT isolation into ft.c Currently netdev handles caching FT auth information and uses FT parsers/auth-proto to manage the protocol. This sets up to remove this state machine from netdev and isolate it into ft.c. This does not break the existing auth-proto (hence the slight modifications, which will be removed soon). Eventually the auth-proto will be removed from FT entirely, replaced just by an FT state machine, similar to how EAPoL works (netdev hooks to TX/RX frames). --- src/ft.c | 319 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/ft.h | 22 +++- src/netdev.c | 29 +++-- 3 files changed, 337 insertions(+), 33 deletions(-) diff --git a/src/ft.c b/src/ft.c index 2285a86f..9d18f0a9 100644 --- a/src/ft.c +++ b/src/ft.c @@ -33,6 +33,30 @@ #include "src/mpdu.h" #include "src/auth-proto.h" #include "src/band.h" +#include "src/scan.h" +#include "src/util.h" +#include "src/netdev.h" +#include "src/module.h" + +static ft_tx_frame_func_t tx_frame = NULL; +static ft_tx_associate_func_t tx_assoc = NULL; +static struct l_queue *info_list = NULL; + +struct ft_info { + uint32_t ifindex; + uint8_t spa[6]; + uint8_t aa[6]; + uint8_t snonce[32]; + uint8_t mde[3]; + uint8_t *fte; + uint8_t *authenticator_ie; + uint8_t prev_bssid[6]; + uint32_t frequency; + + struct ie_ft_info ft_info; + + bool parsed : 1; +}; struct ft_sm { struct auth_proto ap; @@ -45,6 +69,8 @@ struct ft_sm { void *user_data; bool over_ds : 1; + + uint8_t prev_bssid[6]; }; /* @@ -202,11 +228,13 @@ static bool ft_parse_associate_resp_frame(const uint8_t *frame, size_t frame_len return true; } -static int ft_tx_reassociate(struct ft_sm *ft) +static int ft_tx_reassociate(uint32_t ifindex, uint32_t freq, + const uint8_t *prev_bssid) { + struct netdev *netdev = netdev_find(ifindex); + struct handshake_state *hs = netdev_get_handshake(netdev); struct iovec iov[3]; int iov_elems = 0; - struct handshake_state *hs = ft->hs; uint32_t kck_len = handshake_state_get_kck_len(hs); bool is_rsn = hs->supplicant_ie != NULL; uint8_t *rsne = NULL; @@ -233,9 +261,8 @@ static int ft_tx_reassociate(struct ft_sm *ft) rsn_info.num_pmkids = 1; rsn_info.pmkids = hs->pmk_r1_name; - /* Always set OCVC false for FT-over-DS */ - if (ft->over_ds) - rsn_info.ocvc = false; + /* Always set OCVC false for FT for now */ + rsn_info.ocvc = false; rsne = alloca(256); ie_build_rsne(&rsn_info, rsne); @@ -306,7 +333,7 @@ static int ft_tx_reassociate(struct ft_sm *ft) iov_elems += 1; } - return ft->tx_assoc(iov, iov_elems, ft->user_data); + return tx_assoc(ifindex, freq, prev_bssid, iov, iov_elems); error: return -EINVAL; @@ -348,7 +375,7 @@ static bool ft_verify_rsne(const uint8_t *rsne, const uint8_t *pmk_r0_name, return true; } -static int ft_parse_ies(struct handshake_state *hs, +static int parse_ies(struct handshake_state *hs, const uint8_t *authenticator_ie, const uint8_t *ies, size_t ies_len, const uint8_t **mde_out, @@ -480,7 +507,7 @@ bool ft_over_ds_parse_action_ies(struct ft_ds_info *info, const uint8_t *fte = NULL; bool is_rsn = hs->supplicant_ie != NULL; - if (ft_parse_ies(hs, info->authenticator_ie, ies, ies_len, + if (parse_ies(hs, info->authenticator_ie, ies, ies_len, &mde, &fte) < 0) return false; @@ -512,7 +539,7 @@ static int ft_process_ies(struct handshake_state *hs, const uint8_t *ies, if (!ies) goto ft_error; - if (ft_parse_ies(hs, hs->authenticator_ie, ies, ies_len, + if (parse_ies(hs, hs->authenticator_ie, ies, ies_len, &mde, &fte) < 0) goto ft_error; @@ -654,11 +681,10 @@ auth_error: return (int)status_code; } -static int ft_rx_associate(struct auth_proto *ap, const uint8_t *frame, - size_t frame_len) +int __ft_rx_associate(uint32_t ifindex, const uint8_t *frame, size_t frame_len) { - struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap); - struct handshake_state *hs = ft->hs; + struct netdev *netdev = netdev_find(ifindex); + struct handshake_state *hs = netdev_get_handshake(netdev); uint32_t kck_len = handshake_state_get_kck_len(hs); const uint8_t *rsne = NULL; const uint8_t *mde = NULL; @@ -671,6 +697,9 @@ static int ft_rx_associate(struct auth_proto *ap, const uint8_t *frame, &mde, &fte)) return -EBADMSG; + if (out_status != 0) + return (int)out_status; + /* * During a transition in an RSN, check for an RSNE containing the * PMK-R1-Name and the remaining fields same as in the advertised @@ -785,17 +814,25 @@ static int ft_rx_associate(struct auth_proto *ap, const uint8_t *frame, ft_info.igtk_ipn); } - handshake_state_install_ptk(ft->hs); + handshake_state_install_ptk(hs); } return 0; } +static int ft_rx_associate(struct auth_proto *ap, const uint8_t *frame, + size_t frame_len) +{ + struct ft_sm *sm = l_container_of(ap, struct ft_sm, ap); + + return __ft_rx_associate(sm->hs->ifindex, frame, frame_len); +} + static int ft_rx_oci(struct auth_proto *ap) { struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap); - return ft_tx_reassociate(ft); + return ft_tx_reassociate(ft->hs->ifindex, 0, ft->prev_bssid); } static void ft_sm_free(struct auth_proto *ap) @@ -809,7 +846,7 @@ static bool ft_over_ds_start(struct auth_proto *ap) { struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap); - return ft_tx_reassociate(ft) == 0; + return ft_tx_reassociate(ft->hs->ifindex, 0, ft->prev_bssid) == 0; } bool ft_build_authenticate_ies(struct handshake_state *hs, bool ocvc, @@ -924,6 +961,8 @@ struct auth_proto *ft_over_air_sm_new(struct handshake_state *hs, ft->ap.free = ft_sm_free; ft->ap.rx_oci = ft_rx_oci; + memcpy(ft->prev_bssid, hs->aa, 6); + return &ft->ap; } @@ -942,5 +981,253 @@ struct auth_proto *ft_over_ds_sm_new(struct handshake_state *hs, ft->ap.start = ft_over_ds_start; ft->ap.free = ft_sm_free; + memcpy(ft->prev_bssid, hs->aa, 6); + return &ft->ap; } + +void __ft_set_tx_frame_func(ft_tx_frame_func_t func) +{ + tx_frame = func; +} + +void __ft_set_tx_associate_func(ft_tx_associate_func_t func) +{ + tx_assoc = func; +} + +static bool ft_parse_ies(struct ft_info *info, struct handshake_state *hs, + const uint8_t *ies, size_t ies_len) +{ + const uint8_t *mde = NULL; + const uint8_t *fte = NULL; + bool is_rsn = hs->supplicant_ie != NULL; + + if (parse_ies(hs, info->authenticator_ie, ies, ies_len, + &mde, &fte) < 0) + return false; + + if (!mde_equal(info->mde, mde)) + goto ft_error; + + if (is_rsn) { + if (!ft_parse_fte(hs, info->snonce, fte, &info->ft_info)) + goto ft_error; + + info->fte = l_memdup(fte, fte[1] + 2); + } else if (fte) + goto ft_error; + + return true; + +ft_error: + return false; +} + +static struct ft_info *ft_info_find(uint32_t ifindex, const uint8_t *aa) +{ + const struct l_queue_entry *e; + + for (e = l_queue_get_entries(info_list); e; e = e->next) { + struct ft_info *info = e->data; + + if (info->ifindex != ifindex) + continue; + + if (aa && memcmp(info->aa, aa, 6)) + continue; + + return info; + } + + return NULL; +} + +void __ft_rx_action(uint32_t ifindex, const uint8_t *frame, size_t frame_len) +{ + struct netdev *netdev = netdev_find(ifindex); + struct handshake_state *hs = netdev_get_handshake(netdev); + struct ft_info *info; + int ret; + const uint8_t *aa = NULL; + const uint8_t *spa = NULL; + const uint8_t *ies = NULL; + size_t ies_len = 0; + + ret = ft_over_ds_parse_action_response(frame, frame_len, &spa, &aa, + &ies, &ies_len); + if (ret != 0) + return; + + info = ft_info_find(ifindex, aa); + if (!info) + return; + + if (!ft_parse_ies(info, hs, ies, ies_len)) + goto ft_error; + + info->parsed = true; + + return; + +ft_error: + l_debug("FT-over-DS authenticate to "MAC" failed", MAC_STR(info->aa)); +} + +static struct ft_info *ft_info_new(struct handshake_state *hs, + const struct scan_bss *target_bss) +{ + struct ft_info *info = l_new(struct ft_info, 1); + + info->ifindex = hs->ifindex; + memcpy(info->spa, hs->spa, 6); + memcpy(info->aa, target_bss->addr, 6); + memcpy(info->mde, target_bss->mde, sizeof(info->mde)); + memcpy(info->prev_bssid, hs->aa, 6); + + info->frequency = target_bss->frequency; + + if (target_bss->rsne) + info->authenticator_ie = l_memdup(target_bss->rsne, + target_bss->rsne[1] + 2); + + l_getrandom(info->snonce, 32); + + return info; +} + +static void ft_info_destroy(void *data) +{ + struct ft_info *info = data; + + if (info->fte) + l_free(info->fte); + + if (info->authenticator_ie) + l_free(info->authenticator_ie); + + l_free(info); +} + +static void ft_prepare_handshake(struct ft_info *info, + struct handshake_state *hs) +{ + if (!hs->supplicant_ie) + return; + + memcpy(hs->snonce, info->snonce, sizeof(hs->snonce)); + + handshake_state_set_fte(hs, info->fte); + + handshake_state_set_anonce(hs, info->ft_info.anonce); + + handshake_state_set_kh_ids(hs, info->ft_info.r0khid, + info->ft_info.r0khid_len, + info->ft_info.r1khid); + + handshake_state_derive_ptk(hs); +} + +int ft_action(uint32_t ifindex, uint32_t freq, const struct scan_bss *target) +{ + struct netdev *netdev = netdev_find(ifindex); + struct handshake_state *hs = netdev_get_handshake(netdev); + struct ft_info *info; + uint8_t ft_req[14]; + struct iovec iov[5]; + uint8_t ies[512]; + size_t len; + int ret = -EINVAL; + + info = ft_info_new(hs, target); + + ft_req[0] = 6; /* FT category */ + ft_req[1] = 1; /* FT Request action */ + memcpy(ft_req + 2, info->spa, 6); + memcpy(ft_req + 8, info->aa, 6); + + if (!ft_build_authenticate_ies(hs, hs->supplicant_ocvc, info->snonce, + ies, &len)) + goto failed; + + iov[0].iov_base = ft_req; + iov[0].iov_len = sizeof(ft_req); + + iov[1].iov_base = ies; + iov[1].iov_len = len; + + ret = tx_frame(hs->ifindex, 0x00d0, freq, hs->aa, iov, 2); + if (ret < 0) + goto failed; + + l_queue_push_tail(info_list, info); + + return 0; + +failed: + l_free(info); + return ret; +} + +int ft_associate(uint32_t ifindex, const uint8_t *addr) +{ + struct netdev *netdev = netdev_find(ifindex); + struct handshake_state *hs = netdev_get_handshake(netdev); + struct ft_info *info; + int ret; + + /* + * TODO: Since FT-over-DS is done early, before the time of roaming, it + * may end up that a completely new BSS is the best candidate and + * we haven't yet authenticated. We could actually authenticate + * at this point, but for now just assume the caller will choose + * a different BSS. + */ + info = ft_info_find(ifindex, addr); + if (!info) + return -ENOENT; + + ft_prepare_handshake(info, hs); + + ret = ft_tx_reassociate(ifindex, info->frequency, info->prev_bssid); + + /* After this no previous auths will be valid */ + ft_clear_authentications(ifindex); + + return ret; +} + +static bool remove_ifindex(void *data, void *user_data) +{ + struct ft_info *info = data; + uint32_t ifindex = L_PTR_TO_UINT(user_data); + + if (info->ifindex != ifindex) + return false; + + ft_info_destroy(info); + return true; +} + +void ft_clear_authentications(uint32_t ifindex) +{ + l_queue_foreach_remove(info_list, remove_ifindex, + L_UINT_TO_PTR(ifindex)); +} + +static int ft_init(void) +{ + info_list = l_queue_new(); + + return 0; +} + +static void ft_exit(void) +{ + if (!l_queue_isempty(info_list)) + l_warn("stale FT info objects found!"); + + l_queue_destroy(info_list, ft_info_destroy); +} + +IWD_MODULE(ft, ft_init, ft_exit); diff --git a/src/ft.h b/src/ft.h index f90fc1b2..fd6dfdae 100644 --- a/src/ft.h +++ b/src/ft.h @@ -21,11 +21,19 @@ */ struct handshake_state; +struct scan_bss; + +typedef int (*ft_tx_frame_func_t)(uint32_t ifindex, uint16_t frame_type, + uint32_t frequency, + const uint8_t *dest, struct iovec *iov, + size_t iov_len); typedef void (*ft_tx_authenticate_func_t)(struct iovec *iov, size_t iov_len, void *user_data); -typedef int (*ft_tx_associate_func_t)(struct iovec *ie_iov, size_t iov_len, - void *user_data); + +typedef int (*ft_tx_associate_func_t)(uint32_t ifindex, uint32_t freq, + const uint8_t *prev_bssid, + struct iovec *ie_iov, size_t iov_len); typedef int (*ft_get_oci)(void *user_data); typedef void (*ft_ds_free_func_t)(void *user_data); @@ -71,3 +79,13 @@ struct auth_proto *ft_over_ds_sm_new(struct handshake_state *hs, bool ft_over_ds_prepare_handshake(struct ft_ds_info *info, struct handshake_state *hs); + +void __ft_set_tx_frame_func(ft_tx_frame_func_t func); +void __ft_set_tx_associate_func(ft_tx_associate_func_t func); +int __ft_rx_associate(uint32_t ifindex, const uint8_t *frame, + size_t frame_len); +void __ft_rx_action(uint32_t ifindex, const uint8_t *frame, size_t frame_len); + +void ft_clear_authentications(uint32_t ifindex); +int ft_action(uint32_t ifindex, uint32_t freq, const struct scan_bss *target); +int ft_associate(uint32_t ifindex, const uint8_t *addr); diff --git a/src/netdev.c b/src/netdev.c index 5af6bb36..e4ec2ff6 100644 --- a/src/netdev.c +++ b/src/netdev.c @@ -4422,11 +4422,11 @@ restore_snonce: MMPDU_STATUS_CODE_UNSPECIFIED); } -static int netdev_ft_tx_associate(struct iovec *ft_iov, size_t n_ft_iov, - void *user_data) +static int netdev_ft_tx_associate(uint32_t ifindex, uint32_t freq, + const uint8_t *prev_bssid, + struct iovec *ft_iov, size_t n_ft_iov) { - struct netdev *netdev = user_data; - struct auth_proto *ap = netdev->ap; + struct netdev *netdev = netdev_find(ifindex); struct handshake_state *hs = netdev->handshake; struct l_genl_msg *msg; struct iovec iov[64]; @@ -4447,7 +4447,7 @@ static int netdev_ft_tx_associate(struct iovec *ft_iov, size_t n_ft_iov, mpdu_sort_ies(subtype, iov, c_iov); l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN, - ap->prev_bssid); + prev_bssid); l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, c_iov); netdev->connect_cmd_id = l_genl_family_send(nl80211, msg, @@ -4659,17 +4659,15 @@ int netdev_fast_transition(struct netdev *netdev, l_get_le16(target_bss->mde)) return -EINVAL; - prepare_ft(netdev, target_bss); - - handshake_state_new_snonce(netdev->handshake); - netdev->connect_cb = cb; netdev->ap = ft_over_air_sm_new(netdev->handshake, netdev_ft_tx_authenticate, netdev_ft_tx_associate, netdev_get_oci, netdev); - memcpy(netdev->ap->prev_bssid, orig_bss->addr, ETH_ALEN); + prepare_ft(netdev, target_bss); + + handshake_state_new_snonce(netdev->handshake); wiphy_radio_work_insert(netdev->wiphy, &netdev->work, WIPHY_WORK_PRIORITY_CONNECT, &ft_work_ops); @@ -4701,16 +4699,14 @@ int netdev_fast_transition_over_ds(struct netdev *netdev, if (!info || !info->parsed) return -ENOENT; - prepare_ft(netdev, target_bss); - - ft_over_ds_prepare_handshake(&info->super, netdev->handshake); - netdev->connect_cb = cb; netdev->ap = ft_over_ds_sm_new(netdev->handshake, netdev_ft_tx_associate, netdev); - memcpy(netdev->ap->prev_bssid, orig_bss->addr, ETH_ALEN); + prepare_ft(netdev, target_bss); + + ft_over_ds_prepare_handshake(&info->super, netdev->handshake); wiphy_radio_work_insert(netdev->wiphy, &netdev->work, WIPHY_WORK_PRIORITY_CONNECT, &ft_work_ops); @@ -6656,6 +6652,8 @@ static int netdev_init(void) __eapol_set_tx_packet_func(netdev_control_port_frame); __eapol_set_install_pmk_func(netdev_set_pmk); + __ft_set_tx_associate_func(netdev_ft_tx_associate); + unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME, netdev_unicast_notify, NULL, NULL); @@ -6720,3 +6718,4 @@ IWD_MODULE(netdev, netdev_init, netdev_exit); IWD_MODULE_DEPENDS(netdev, eapol); IWD_MODULE_DEPENDS(netdev, frame_xchg); IWD_MODULE_DEPENDS(netdev, wiphy); +IWD_MODULE_DEPENDS(netdev, ft);