From fd8671e9c54d11c0efc741017336b62a1f118652 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Mon, 13 Aug 2018 16:25:48 -0700 Subject: [PATCH] netdev: added sae functionality to netdev In order to plug SAE into the existing connect mechanism the actual CMD_CONNECT message is never sent, rather sae_register takes care of sending out CMD_AUTHENTICATE. This required some shuffling of code in order to handle both eapol and sae. In the case of non-SAE authentication everything behaves as it did before. When using SAE an sae_sm is created when a connection is attempted but the eapol_sm is not. After SAE succeeds it will start association and then create the eapol_sm and start the 4-way handshake. This change also adds the handshake SAE events to device and initializes SAE in main. --- src/device.c | 13 ++- src/netdev.c | 255 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 201 insertions(+), 67 deletions(-) diff --git a/src/device.c b/src/device.c index abefc5f2..b2126b1b 100644 --- a/src/device.c +++ b/src/device.c @@ -779,10 +779,15 @@ static struct handshake_state *device_handshake_setup(struct device *device, handshake_state_set_own_wpa(hs, rsne_buf); } - if (security == SECURITY_PSK) - handshake_state_set_pmk(hs, network_get_psk(network), - 32); - else + if (security == SECURITY_PSK) { + /* SAE will generate/set the PMK */ + if (info.akm_suites == IE_RSN_AKM_SUITE_SAE_SHA256) + handshake_state_set_passphrase(hs, + network_get_passphrase(network)); + else + handshake_state_set_pmk(hs, + network_get_psk(network), 32); + } else handshake_state_set_8021x_config(hs, network_get_settings(network)); diff --git a/src/netdev.c b/src/netdev.c index fedfb8dc..366c73ad 100644 --- a/src/netdev.c +++ b/src/netdev.c @@ -54,6 +54,7 @@ #include "src/ftutil.h" #include "src/util.h" #include "src/watchlist.h" +#include "src/sae.h" #ifndef ENOTSUPP #define ENOTSUPP 524 @@ -85,6 +86,7 @@ struct netdev { netdev_adhoc_cb_t adhoc_cb; void *user_data; struct eapol_sm *sm; + struct sae_sm *sae_sm; struct handshake_state *handshake; uint32_t connect_cmd_id; uint32_t disconnect_cmd_id; @@ -507,6 +509,11 @@ static void netdev_connect_free(struct netdev *netdev) netdev->sm = NULL; } + if (netdev->sae_sm) { + sae_sm_free(netdev->sae_sm); + netdev->sae_sm = NULL; + } + eapol_preauth_cancel(netdev->index); if (netdev->handshake) { @@ -1962,68 +1969,19 @@ static void netdev_cmd_ft_reassociate_cb(struct l_genl_msg *msg, } } -static void netdev_authenticate_event(struct l_genl_msg *msg, - struct netdev *netdev) +static void netdev_ft_process(struct netdev *netdev, const uint8_t *frame, + size_t frame_len) { struct l_genl_msg *cmd_associate, *cmd_deauth; - struct l_genl_attr attr; - uint16_t type, len; - const void *data; uint16_t status_code; const uint8_t *ies = NULL; size_t ies_len; - const uint8_t *frame = NULL; - size_t frame_len = 0; struct ie_tlv_iter iter; const uint8_t *rsne = NULL; const uint8_t *mde = NULL; const uint8_t *fte = NULL; struct handshake_state *hs = netdev->handshake; bool is_rsn; - - l_debug(""); - - if (!netdev->connected) { - l_warn("Unexpected connection related event -- " - "is another supplicant running?"); - return; - } - - /* - * During Fast Transition we use the authenticate event to start the - * reassociation step because the FTE necessary before we can build - * the FT Associate command is included in the attached frame and is - * not available in the Authenticate command callback. - */ - if (!netdev->in_ft) - return; - - if (!l_genl_attr_init(&attr, msg)) { - l_debug("attr init failed"); - - goto auth_error; - } - - while (l_genl_attr_next(&attr, &type, &len, &data)) { - switch (type) { - case NL80211_ATTR_TIMED_OUT: - l_warn("authentication timed out"); - - goto auth_error; - - case NL80211_ATTR_FRAME: - if (frame) - goto auth_error; - - frame = data; - frame_len = len; - break; - } - } - - if (!frame) - goto auth_error; - /* * Parse the Authentication Response and validate the contents * according to 12.5.2 / 12.5.4: RSN or non-RSN Over-the-air @@ -2200,6 +2158,85 @@ ft_error: netdev, NULL); } +static void netdev_sae_process(struct netdev *netdev, const uint8_t *from, + const uint8_t *frame, size_t frame_len) +{ + sae_rx_packet(netdev->sae_sm, from, frame, frame_len); +} + +static void netdev_authenticate_event(struct l_genl_msg *msg, + struct netdev *netdev) +{ + struct l_genl_attr attr; + uint16_t type, len; + const void *data; + const uint8_t *frame = NULL; + size_t frame_len = 0; + + l_debug(""); + + if (!netdev->connected) { + l_warn("Unexpected connection related event -- " + "is another supplicant running?"); + return; + } + + /* + * During Fast Transition we use the authenticate event to start the + * reassociation step because the FTE necessary before we can build + * the FT Associate command is included in the attached frame and is + * not available in the Authenticate command callback. + */ + if (!netdev->in_ft && !netdev->sae_sm) + return; + + if (!l_genl_attr_init(&attr, msg)) { + l_debug("attr init failed"); + + goto auth_error; + } + + while (l_genl_attr_next(&attr, &type, &len, &data)) { + switch (type) { + case NL80211_ATTR_TIMED_OUT: + l_warn("authentication timed out"); + + if (netdev->sae_sm) { + sae_timeout(netdev->sae_sm); + return; + } + + goto auth_error; + + case NL80211_ATTR_FRAME: + if (frame) + goto auth_error; + + frame = data; + frame_len = len; + break; + } + } + + if (!frame) + goto auth_error; + + if (netdev->in_ft) + netdev_ft_process(netdev, frame, frame_len); + else if (netdev->sae_sm) + netdev_sae_process(netdev, + ((struct mmpdu_header *)frame)->address_2, + frame + 26, frame_len - 26); + else + goto auth_error; + + return; + +auth_error: + netdev->result = NETDEV_RESULT_AUTHENTICATION_FAILED; + netdev_connect_failed(NULL, netdev); +} + static void netdev_associate_event(struct l_genl_msg *msg, struct netdev *netdev) { @@ -2219,6 +2256,12 @@ static void netdev_cmd_connect_cb(struct l_genl_msg *msg, void *user_data) NETDEV_EVENT_ASSOCIATING, netdev->user_data); + /* the SAE SM can be freed */ + if (netdev->sae_sm) { + sae_sm_free(netdev->sae_sm); + netdev->sae_sm = NULL; + } + /* * We register the eapol state machine here, in case the PAE * socket receives EAPoL packets before the nl80211 socket @@ -2236,6 +2279,81 @@ static void netdev_cmd_connect_cb(struct l_genl_msg *msg, void *user_data) netdev_connect_failed(NULL, netdev); } +static void netdev_sae_complete(uint16_t status, void *user_data) +{ + struct netdev *netdev = user_data; + struct l_genl_msg *msg; + + if (status != 0) { + l_error("SAE exchange failed on %u result %u", + netdev->index, status); + netdev->sae_sm = NULL; + goto auth_failed; + } + + msg = netdev_build_cmd_associate_common(netdev); + + l_genl_msg_append_attr(msg, NL80211_ATTR_IE, + netdev->handshake->own_ie[1] + 2, + netdev->handshake->own_ie); + + /* netdev_cmd_connect_cb can be reused */ + netdev->connect_cmd_id = l_genl_family_send(nl80211, msg, + netdev_cmd_connect_cb, + netdev, NULL); + + if (!netdev->connect_cmd_id) + goto auth_failed; + + /* + * Kick off EAPoL sm early in case the first EAPoL packet comes prior + * to the netdev_cmd_connect_cb + */ + netdev->sm = eapol_sm_new(netdev->handshake); + return; + +auth_failed: + netdev->result = NETDEV_RESULT_AUTHENTICATION_FAILED; + netdev_connect_failed(NULL, netdev); +} + +static void netdev_tx_sae_frame_cb(struct l_genl_msg *msg, + void *user_data) +{ + int err = l_genl_msg_get_error(msg); + + if (err < 0) + l_debug("SAE: CMD_AUTHENTICATE failed: %s", strerror(err)); +} + +static int netdev_tx_sae_frame(const uint8_t *dest, const uint8_t *body, + size_t body_len, void *user_data) +{ + struct netdev *netdev = user_data; + struct l_genl_msg *msg; + uint32_t auth_type = NL80211_AUTHTYPE_SAE; + + msg = l_genl_msg_new_sized(NL80211_CMD_AUTHENTICATE, 512); + l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); + l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, + 4, &netdev->frequency); + l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, dest); + l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, + strlen((char *) netdev->handshake->ssid), + netdev->handshake->ssid); + l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_TYPE, 4, &auth_type); + + l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_DATA, body_len, body); + + if (!l_genl_family_send(nl80211, msg, netdev_tx_sae_frame_cb, + netdev, NULL)) { + l_genl_msg_unref(msg); + return -EINVAL; + } + + return 0; +} + static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev, struct scan_bss *bss, struct handshake_state *hs, @@ -2337,13 +2455,15 @@ static int netdev_connect_common(struct netdev *netdev, netdev_event_func_t event_filter, netdev_connect_cb_t cb, void *user_data) { - netdev->connect_cmd_id = l_genl_family_send(nl80211, cmd_connect, - netdev_cmd_connect_cb, - netdev, NULL); + if (cmd_connect) { + netdev->connect_cmd_id = l_genl_family_send(nl80211, + cmd_connect, netdev_cmd_connect_cb, + netdev, NULL); - if (!netdev->connect_cmd_id) { - l_genl_msg_unref(cmd_connect); - return -EIO; + if (!netdev->connect_cmd_id) { + l_genl_msg_unref(cmd_connect); + return -EIO; + } } netdev->event_filter = event_filter; @@ -2360,6 +2480,9 @@ static int netdev_connect_common(struct netdev *netdev, handshake_state_set_authenticator_address(hs, bss->addr); handshake_state_set_supplicant_address(hs, netdev->addr); + if (netdev->sae_sm) + sae_start(netdev->sae_sm); + return 0; } @@ -2368,7 +2491,7 @@ int netdev_connect(struct netdev *netdev, struct scan_bss *bss, netdev_event_func_t event_filter, netdev_connect_cb_t cb, void *user_data) { - struct l_genl_msg *cmd_connect; + struct l_genl_msg *cmd_connect = NULL; struct eapol_sm *sm = NULL; bool is_rsn = hs->own_ie != NULL; @@ -2378,12 +2501,18 @@ int netdev_connect(struct netdev *netdev, struct scan_bss *bss, if (netdev->connected) return -EISCONN; - cmd_connect = netdev_build_cmd_connect(netdev, bss, hs, NULL); - if (!cmd_connect) - return -EINVAL; + if (hs->akm_suite == IE_RSN_AKM_SUITE_SAE_SHA256) { + netdev->sae_sm = sae_sm_new(hs, netdev_tx_sae_frame, + netdev_sae_complete, netdev); + } else { + cmd_connect = netdev_build_cmd_connect(netdev, bss, hs, NULL); - if (is_rsn) - sm = eapol_sm_new(hs); + if (!cmd_connect) + return -EINVAL; + + if (is_rsn) + sm = eapol_sm_new(hs); + } return netdev_connect_common(netdev, cmd_connect, bss, hs, sm, event_filter, cb, user_data);