netdev: ft: refactor FT into an auth-proto

Since FT operates over Authenticate/Associate, it makes the most sense
for it to behave like the other auth-protos.

This change moves all the FT specific processing out of netdev and into
ft.c. The bulk of the changes were strait copy-pastes from netdev into
ft.c with minor API changes (e.g. remove struct netdev).

The 'in_ft' boolean unforunately is still required for a few reasons:

 - netdev_disconnect_event relies on this flag so it can ignore the
   disconnect which comes in when doing a fast transition. We cannot
   simply check netdev->ap because this would cause the other auth-protos
   to not handle a disconnect correctly.
 - netdev_associate_event needs to correctly setup the eapol_sm when
   in FT mode by setting require_handshake and use_eapol_start to false.
   This cannot be handled inside eapol by checking the AKM because an AP
   may only advertise a FT AKM, and the initial mobility association
   does require the 4-way handshake.
This commit is contained in:
James Prestwood 2019-05-07 11:53:42 -07:00 committed by Denis Kenzior
parent 87346212c9
commit 567f35c32f
3 changed files with 536 additions and 512 deletions

452
src/ft.c
View File

@ -24,8 +24,6 @@
#include <config.h>
#endif
#include "linux/nl80211.h"
#include <ell/ell.h>
#include "src/ie.h"
@ -33,12 +31,23 @@
#include "src/crypto.h"
#include "src/ft.h"
#include "src/mpdu.h"
#include "src/auth-proto.h"
struct ft_sm {
struct auth_proto ap;
struct handshake_state *hs;
ft_tx_authenticate_func_t tx_auth;
ft_tx_associate_func_t tx_assoc;
void *user_data;
};
/*
* Calculate the MIC field of the FTE and write it directly to that FTE,
* assuming it was all zeros before. See 12.8.4 and 12.8.5.
*/
bool ft_calculate_fte_mic(struct handshake_state *hs, uint8_t seq_num,
static bool ft_calculate_fte_mic(struct handshake_state *hs, uint8_t seq_num,
const uint8_t *rsne, const uint8_t *fte,
const uint8_t *ric, uint8_t *out_mic)
{
@ -98,7 +107,7 @@ bool ft_calculate_fte_mic(struct handshake_state *hs, uint8_t seq_num,
* the start of the IE array (RSN, MD, FT, TI and RIC).
* See 8.3.3.1 for the header and 8.3.3.11 for the body format.
*/
bool ft_parse_authentication_resp_frame(const uint8_t *data, size_t len,
static bool ft_parse_authentication_resp_frame(const uint8_t *data, size_t len,
const uint8_t *addr1, const uint8_t *addr2,
const uint8_t *addr3, uint16_t auth_seq,
uint16_t *out_status, const uint8_t **out_ies,
@ -121,8 +130,8 @@ bool ft_parse_authentication_resp_frame(const uint8_t *data, size_t len,
if (memcmp(data + 16, addr3, 6))
return false;
/* Check Authentication algorithm number is FT */
if (l_get_le16(data + 24) != NL80211_AUTHTYPE_FT)
/* Check Authentication algorithm number is FT (2) */
if (l_get_le16(data + 24) != 2)
return false;
if (l_get_le16(data + 26) != auth_seq)
@ -142,7 +151,7 @@ bool ft_parse_authentication_resp_frame(const uint8_t *data, size_t len,
return true;
}
bool ft_parse_associate_resp_frame(const uint8_t *frame, size_t frame_len,
static bool ft_parse_associate_resp_frame(const uint8_t *frame, size_t frame_len,
uint16_t *out_status, const uint8_t **rsne,
const uint8_t **mde, const uint8_t **fte)
{
@ -188,3 +197,432 @@ 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)
{
struct iovec iov[3];
int iov_elems = 0;
struct handshake_state *hs = ft->hs;
bool is_rsn = hs->supplicant_ie != NULL;
uint8_t *rsne = NULL;
if (is_rsn) {
struct ie_rsn_info rsn_info;
/*
* Rebuild the RSNE to include the PMKR1Name and append
* MDE + FTE.
*
* 12.8.4: "If present, the RSNE shall be set as follows:
* Version field shall be set to 1.
* PMKID Count field shall be set to 1.
* PMKID field shall contain the PMKR1Name.
* All other fields shall be as specified in 8.4.2.27
* and 11.5.3."
*/
if (ie_parse_rsne_from_data(hs->supplicant_ie,
hs->supplicant_ie[1] + 2,
&rsn_info) < 0)
goto error;
rsn_info.num_pmkids = 1;
rsn_info.pmkids = hs->pmk_r1_name;
rsne = alloca(256);
ie_build_rsne(&rsn_info, rsne);
iov[iov_elems].iov_base = rsne;
iov[iov_elems].iov_len = rsne[1] + 2;
iov_elems += 1;
}
/* The MDE advertised by the BSS must be passed verbatim */
iov[iov_elems].iov_base = (void *) hs->mde;
iov[iov_elems].iov_len = hs->mde[1] + 2;
iov_elems += 1;
if (is_rsn) {
struct ie_ft_info ft_info;
uint8_t *fte;
/*
* 12.8.4: "If present, the FTE shall be set as follows:
* ANonce, SNonce, R0KH-ID, and R1KH-ID shall be set to
* the values contained in the second message of this
* sequence.
* The Element Count field of the MIC Control field shall
* be set to the number of elements protected in this
* frame (variable).
* [...]
* All other fields shall be set to 0."
*/
memset(&ft_info, 0, sizeof(ft_info));
ft_info.mic_element_count = 3;
memcpy(ft_info.r0khid, hs->r0khid, hs->r0khid_len);
ft_info.r0khid_len = hs->r0khid_len;
memcpy(ft_info.r1khid, hs->r1khid, 6);
ft_info.r1khid_present = true;
memcpy(ft_info.anonce, hs->anonce, 32);
memcpy(ft_info.snonce, hs->snonce, 32);
fte = alloca(256);
ie_build_fast_bss_transition(&ft_info, fte);
if (!ft_calculate_fte_mic(hs, 5, rsne, fte, NULL, ft_info.mic))
goto error;
/* Rebuild the FT IE now with the MIC included */
ie_build_fast_bss_transition(&ft_info, fte);
iov[iov_elems].iov_base = fte;
iov[iov_elems].iov_len = fte[1] + 2;
iov_elems += 1;
}
ft->tx_assoc(iov, iov_elems, ft->user_data);
return 0;
error:
return -EINVAL;
}
static int ft_rx_authenticate(struct auth_proto *ap, const uint8_t *frame,
size_t frame_len)
{
struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap);
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
const uint8_t *ies = NULL;
size_t ies_len;
struct ie_tlv_iter iter;
const uint8_t *rsne = NULL;
const uint8_t *mde = NULL;
const uint8_t *fte = NULL;
struct handshake_state *hs = ft->hs;
bool is_rsn;
/*
* Parse the Authentication Response and validate the contents
* according to 12.5.2 / 12.5.4: RSN or non-RSN Over-the-air
* FT Protocol.
*/
if (!ft_parse_authentication_resp_frame(frame, frame_len,
hs->spa, hs->aa, hs->aa, 2,
&status_code, &ies, &ies_len))
goto auth_error;
/* AP Rejected the authenticate / associate */
if (status_code != 0)
goto auth_error;
/* Check 802.11r IEs */
if (!ies)
goto ft_error;
ie_tlv_iter_init(&iter, ies, ies_len);
while (ie_tlv_iter_next(&iter)) {
switch (ie_tlv_iter_get_tag(&iter)) {
case IE_TYPE_RSN:
if (rsne)
goto ft_error;
rsne = ie_tlv_iter_get_data(&iter) - 2;
break;
case IE_TYPE_MOBILITY_DOMAIN:
if (mde)
goto ft_error;
mde = ie_tlv_iter_get_data(&iter) - 2;
break;
case IE_TYPE_FAST_BSS_TRANSITION:
if (fte)
goto ft_error;
fte = ie_tlv_iter_get_data(&iter) - 2;
break;
}
}
is_rsn = hs->supplicant_ie != NULL;
/*
* In an RSN, check for an RSNE containing the PMK-R0-Name and
* the remaining fields same as in the advertised RSNE.
*
* 12.8.3: "The RSNE shall be present only if dot11RSNAActivated
* is true. If present, the RSNE shall be set as follows:
* Version field shall be set to 1.
* PMKID Count field shall be set to 1.
* PMKID List field shall be set to the value contained in the
* first message of this sequence.
* All other fields shall be identical to the contents of the
* RSNE advertised by the AP in Beacon and Probe Response frames."
*/
if (is_rsn) {
struct ie_rsn_info msg2_rsne;
if (!rsne)
goto ft_error;
if (ie_parse_rsne_from_data(rsne, rsne[1] + 2,
&msg2_rsne) < 0)
goto ft_error;
if (msg2_rsne.num_pmkids != 1 ||
memcmp(msg2_rsne.pmkids, hs->pmk_r0_name, 16))
goto ft_error;
if (!handshake_util_ap_ie_matches(rsne, hs->authenticator_ie,
false))
goto ft_error;
} else if (rsne)
goto ft_error;
/*
* Check for an MD IE identical to the one we sent in message 1
*
* 12.8.3: "The MDE shall contain the MDID and FT Capability and
* Policy fields. This element shall be the same as the MDE
* advertised by the target AP in Beacon and Probe Response frames."
*/
if (!mde || memcmp(hs->mde, mde, hs->mde[1] + 2))
goto ft_error;
/*
* In an RSN, check for an FT IE with the same R0KH-ID and the same
* SNonce that we sent, and check that the R1KH-ID and the ANonce
* are present. Use them to generate new PMK-R1, PMK-R1-Name and PTK
* in handshake.c.
*
* 12.8.3: "The FTE shall be present only if dot11RSNAActivated is
* true. If present, the FTE shall be set as follows:
* R0KH-ID shall be identical to the R0KH-ID provided by the FTO
* in the first message.
* R1KH-ID shall be set to the R1KH-ID of the target AP, from
* dot11FTR1KeyHolderID.
* ANonce shall be set to a value chosen randomly by the target AP,
* following the recommendations of 11.6.5.
* SNonce shall be set to the value contained in the first message
* of this sequence.
* All other fields shall be set to 0."
*/
if (is_rsn) {
struct ie_ft_info ft_info;
uint8_t zeros[16] = {};
if (!fte)
goto ft_error;
if (ie_parse_fast_bss_transition_from_data(fte, fte[1] + 2,
&ft_info) < 0)
goto ft_error;
if (ft_info.mic_element_count != 0 ||
memcmp(ft_info.mic, zeros, 16))
goto ft_error;
if (hs->r0khid_len != ft_info.r0khid_len ||
memcmp(hs->r0khid, ft_info.r0khid,
hs->r0khid_len) ||
!ft_info.r1khid_present)
goto ft_error;
if (memcmp(ft_info.snonce, hs->snonce, 32))
goto ft_error;
handshake_state_set_fte(hs, fte);
handshake_state_set_anonce(hs, ft_info.anonce);
handshake_state_set_kh_ids(hs, ft_info.r0khid,
ft_info.r0khid_len,
ft_info.r1khid);
handshake_state_derive_ptk(hs);
} else if (fte)
goto ft_error;
return ft_tx_reassociate(ft);
auth_error:
return (int)status_code;
ft_error:
return -EBADMSG;
}
static int ft_rx_associate(struct auth_proto *ap, 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;
const uint8_t *rsne = NULL;
const uint8_t *mde = NULL;
const uint8_t *fte = NULL;
const uint8_t *sent_mde = hs->mde;
bool is_rsn = hs->supplicant_ie != NULL;
uint16_t out_status;
if (!ft_parse_associate_resp_frame(frame, frame_len, &out_status, &rsne,
&mde, &fte))
return -EBADMSG;
/*
* During a transition in an RSN, check for an RSNE containing the
* PMK-R1-Name and the remaining fields same as in the advertised
* RSNE.
*
* 12.8.5: "The RSNE shall be present only if dot11RSNAActivated is
* true. If present, the RSNE shall be set as follows:
* Version field shall be set to 1.
* PMKID Count field shall be set to 1.
* PMKID field shall contain the PMKR1Name
* All other fields shall be identical to the contents of the RSNE
* advertised by the target AP in Beacon and Probe Response frames."
*/
if (is_rsn) {
struct ie_rsn_info msg4_rsne;
if (!rsne)
return -EBADMSG;
if (ie_parse_rsne_from_data(rsne, rsne[1] + 2,
&msg4_rsne) < 0)
return -EBADMSG;
if (msg4_rsne.num_pmkids != 1 ||
memcmp(msg4_rsne.pmkids, hs->pmk_r1_name, 16))
return -EBADMSG;
if (!handshake_util_ap_ie_matches(rsne, hs->authenticator_ie,
false))
return -EBADMSG;
} else {
if (rsne)
return -EBADMSG;
}
/* An MD IE identical to the one we sent must be present */
if (sent_mde && (!mde || memcmp(sent_mde, mde, sent_mde[1] + 2)))
return -EBADMSG;
/*
* An FT IE is required in an initial mobility domain
* association and re-associations in an RSN but not present
* in a non-RSN (12.4.2 vs. 12.4.3).
*/
if (sent_mde && is_rsn && !fte)
return -EBADMSG;
if (!(sent_mde && is_rsn) && fte)
return -EBADMSG;
if (fte) {
struct ie_ft_info ft_info;
uint8_t mic[16];
if (ie_parse_fast_bss_transition_from_data(fte, fte[1] + 2,
&ft_info) < 0)
return -EBADMSG;
/*
* In an RSN, check for an FT IE with the same
* R0KH-ID, R1KH-ID, ANonce and SNonce that we
* received in message 2, MIC Element Count
* of 6 and the correct MIC.
*/
if (!ft_calculate_fte_mic(hs, 6, rsne, fte, NULL, mic))
return -EBADMSG;
if (ft_info.mic_element_count != 3 ||
memcmp(ft_info.mic, mic, 16))
return -EBADMSG;
if (hs->r0khid_len != ft_info.r0khid_len ||
memcmp(hs->r0khid, ft_info.r0khid,
hs->r0khid_len) ||
!ft_info.r1khid_present ||
memcmp(hs->r1khid, ft_info.r1khid, 6))
return -EBADMSG;
if (memcmp(ft_info.anonce, hs->anonce, 32))
return -EBADMSG;
if (memcmp(ft_info.snonce, hs->snonce, 32))
return -EBADMSG;
if (ft_info.gtk_len) {
uint8_t gtk[32];
if (!handshake_decode_fte_key(hs, ft_info.gtk,
ft_info.gtk_len,
gtk))
return -EBADMSG;
if (ft_info.gtk_rsc[6] != 0x00 ||
ft_info.gtk_rsc[7] != 0x00)
return -EBADMSG;
handshake_state_install_gtk(hs, ft_info.gtk_key_id,
gtk, ft_info.gtk_len,
ft_info.gtk_rsc, 6);
}
if (ft_info.igtk_len) {
uint8_t igtk[16];
if (!handshake_decode_fte_key(hs, ft_info.igtk,
ft_info.igtk_len, igtk))
return -EBADMSG;
handshake_state_install_igtk(hs, ft_info.igtk_key_id,
igtk, ft_info.igtk_len,
ft_info.igtk_ipn);
}
handshake_state_install_ptk(ft->hs);
}
return 0;
}
static void ft_sm_free(struct auth_proto *ap)
{
struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap);
l_free(ft);
}
static bool ft_start(struct auth_proto *ap)
{
struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap);
ft->tx_auth(ft->user_data);
return true;
}
struct auth_proto *ft_sm_new(struct handshake_state *hs,
ft_tx_authenticate_func_t tx_auth,
ft_tx_associate_func_t tx_assoc,
void *user_data)
{
struct ft_sm *ft = l_new(struct ft_sm, 1);
ft->tx_auth = tx_auth;
ft->tx_assoc = tx_assoc;
ft->hs = hs;
ft->user_data = user_data;
ft->ap.rx_authenticate = ft_rx_authenticate;
ft->ap.rx_associate = ft_rx_associate;
ft->ap.start = ft_start;
ft->ap.free = ft_sm_free;
return &ft->ap;
}

View File

@ -20,20 +20,11 @@
*
*/
#include <stdbool.h>
typedef void (*ft_tx_authenticate_func_t)(void *user_data);
typedef void (*ft_tx_associate_func_t)(struct iovec *ie_iov, size_t iov_len,
void *user_data);
struct handshake_state;
bool ft_calculate_fte_mic(struct handshake_state *hs, uint8_t seq_num,
const uint8_t *rsne, const uint8_t *fte,
const uint8_t *ric, uint8_t *out_mic);
bool ft_parse_authentication_resp_frame(const uint8_t *data, size_t len,
const uint8_t *addr1, const uint8_t *addr2,
const uint8_t *addr3, uint16_t auth_seq,
uint16_t *out_status, const uint8_t **out_ies,
size_t *out_ies_len);
bool ft_parse_associate_resp_frame(const uint8_t *frame, size_t frame_len,
uint16_t *out_status, const uint8_t **rsne,
const uint8_t **mde, const uint8_t **fte);
struct auth_proto *ft_sm_new(struct handshake_state *hs,
ft_tx_authenticate_func_t tx_auth,
ft_tx_associate_func_t tx_assoc,
void *user_data);

View File

@ -113,6 +113,7 @@ struct netdev {
struct l_timeout *group_handshake_timeout;
uint16_t sa_query_id;
uint8_t prev_bssid[ETH_ALEN];
uint8_t prev_snonce[32];
int8_t rssi_levels[16];
uint8_t rssi_levels_num;
uint8_t cur_rssi_level_idx;
@ -1535,145 +1536,6 @@ static void netdev_set_rekey_offload(uint32_t ifindex,
netdev, NULL);
}
/*
* Handle the Association Response IE contents either as part of an
* FT initial Mobility Domain association (12.4) or a Fast Transition
* (12.8.5).
*/
static bool netdev_ft_process_associate(struct netdev *netdev,
const uint8_t *frame, size_t frame_len,
uint16_t *out_status)
{
struct handshake_state *hs = netdev->handshake;
const uint8_t *rsne = NULL;
const uint8_t *mde = NULL;
const uint8_t *fte = NULL;
const uint8_t *sent_mde = hs->mde;
bool is_rsn = hs->supplicant_ie != NULL;
if (!ft_parse_associate_resp_frame(frame, frame_len, out_status, &rsne,
&mde, &fte))
return false;
/*
* During a transition in an RSN, check for an RSNE containing the
* PMK-R1-Name and the remaining fields same as in the advertised
* RSNE.
*
* 12.8.5: "The RSNE shall be present only if dot11RSNAActivated is
* true. If present, the RSNE shall be set as follows:
* Version field shall be set to 1.
* PMKID Count field shall be set to 1.
* PMKID field shall contain the PMKR1Name
* All other fields shall be identical to the contents of the RSNE
* advertised by the target AP in Beacon and Probe Response frames."
*/
if (is_rsn) {
struct ie_rsn_info msg4_rsne;
if (!rsne)
return false;
if (ie_parse_rsne_from_data(rsne, rsne[1] + 2,
&msg4_rsne) < 0)
return false;
if (msg4_rsne.num_pmkids != 1 ||
memcmp(msg4_rsne.pmkids, hs->pmk_r1_name, 16))
return false;
if (!handshake_util_ap_ie_matches(rsne, hs->authenticator_ie,
false))
return false;
} else {
if (rsne)
return false;
}
/* An MD IE identical to the one we sent must be present */
if (sent_mde && (!mde || memcmp(sent_mde, mde, sent_mde[1] + 2)))
return false;
/*
* An FT IE is required in an initial mobility domain
* association and re-associations in an RSN but not present
* in a non-RSN (12.4.2 vs. 12.4.3).
*/
if (sent_mde && is_rsn && !fte)
return false;
if (!(sent_mde && is_rsn) && fte)
return false;
if (fte) {
struct ie_ft_info ft_info;
uint8_t mic[16];
if (ie_parse_fast_bss_transition_from_data(fte, fte[1] + 2,
&ft_info) < 0)
return false;
/*
* In an RSN, check for an FT IE with the same
* R0KH-ID, R1KH-ID, ANonce and SNonce that we
* received in message 2, MIC Element Count
* of 6 and the correct MIC.
*/
if (!ft_calculate_fte_mic(hs, 6, rsne, fte, NULL, mic))
return false;
if (ft_info.mic_element_count != 3 ||
memcmp(ft_info.mic, mic, 16))
return false;
if (hs->r0khid_len != ft_info.r0khid_len ||
memcmp(hs->r0khid, ft_info.r0khid,
hs->r0khid_len) ||
!ft_info.r1khid_present ||
memcmp(hs->r1khid, ft_info.r1khid, 6))
return false;
if (memcmp(ft_info.anonce, hs->anonce, 32))
return false;
if (memcmp(ft_info.snonce, hs->snonce, 32))
return false;
if (ft_info.gtk_len) {
uint8_t gtk[32];
if (!handshake_decode_fte_key(hs, ft_info.gtk,
ft_info.gtk_len,
gtk))
return false;
if (ft_info.gtk_rsc[6] != 0x00 ||
ft_info.gtk_rsc[7] != 0x00)
return false;
handshake_state_install_gtk(hs,
ft_info.gtk_key_id,
gtk, ft_info.gtk_len,
ft_info.gtk_rsc, 6);
}
if (ft_info.igtk_len) {
uint8_t igtk[16];
if (!handshake_decode_fte_key(hs, ft_info.igtk,
ft_info.igtk_len, igtk))
return false;
handshake_state_install_igtk(hs,
ft_info.igtk_key_id,
igtk, ft_info.igtk_len,
ft_info.igtk_ipn);
}
}
return true;
}
static void netdev_connect_event(struct l_genl_msg *msg, struct netdev *netdev)
{
struct l_genl_attr attr;
@ -1948,110 +1810,6 @@ static struct l_genl_msg *netdev_build_cmd_associate_common(
return msg;
}
/*
* Build an FT Reassociation Request frame according to 12.5.2 / 12.5.4:
* RSN or non-RSN Over-the-air FT Protocol, and with the IE contents
* according to 12.8.4: FT authentication sequence: contents of third message.
*/
static struct l_genl_msg *netdev_build_cmd_ft_reassociate(struct netdev *netdev)
{
struct l_genl_msg *msg;
struct iovec iov[3];
int iov_elems = 0;
struct handshake_state *hs = netdev_get_handshake(netdev);
bool is_rsn = hs->supplicant_ie != NULL;
uint8_t *rsne = NULL;
msg = netdev_build_cmd_associate_common(netdev);
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN,
netdev->prev_bssid);
if (is_rsn) {
struct ie_rsn_info rsn_info;
/*
* Rebuild the RSNE to include the PMKR1Name and append
* MDE + FTE.
*
* 12.8.4: "If present, the RSNE shall be set as follows:
* Version field shall be set to 1.
* PMKID Count field shall be set to 1.
* PMKID field shall contain the PMKR1Name.
* All other fields shall be as specified in 8.4.2.27
* and 11.5.3."
*/
if (ie_parse_rsne_from_data(hs->supplicant_ie,
hs->supplicant_ie[1] + 2,
&rsn_info) < 0)
goto error;
rsn_info.num_pmkids = 1;
rsn_info.pmkids = hs->pmk_r1_name;
rsne = alloca(256);
ie_build_rsne(&rsn_info, rsne);
iov[iov_elems].iov_base = rsne;
iov[iov_elems].iov_len = rsne[1] + 2;
iov_elems += 1;
}
/* The MDE advertised by the BSS must be passed verbatim */
iov[iov_elems].iov_base = (void *) hs->mde;
iov[iov_elems].iov_len = hs->mde[1] + 2;
iov_elems += 1;
if (is_rsn) {
struct ie_ft_info ft_info;
uint8_t *fte;
/*
* 12.8.4: "If present, the FTE shall be set as follows:
* ANonce, SNonce, R0KH-ID, and R1KH-ID shall be set to
* the values contained in the second message of this
* sequence.
* The Element Count field of the MIC Control field shall
* be set to the number of elements protected in this
* frame (variable).
* [...]
* All other fields shall be set to 0."
*/
memset(&ft_info, 0, sizeof(ft_info));
ft_info.mic_element_count = 3;
memcpy(ft_info.r0khid, hs->r0khid, hs->r0khid_len);
ft_info.r0khid_len = hs->r0khid_len;
memcpy(ft_info.r1khid, hs->r1khid, 6);
ft_info.r1khid_present = true;
memcpy(ft_info.anonce, hs->anonce, 32);
memcpy(ft_info.snonce, hs->snonce, 32);
fte = alloca(256);
ie_build_fast_bss_transition(&ft_info, fte);
if (!ft_calculate_fte_mic(hs, 5, rsne, fte, NULL, ft_info.mic))
goto error;
/* Rebuild the FT IE now with the MIC included */
ie_build_fast_bss_transition(&ft_info, fte);
iov[iov_elems].iov_base = fte;
iov[iov_elems].iov_len = fte[1] + 2;
iov_elems += 1;
}
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, iov_elems);
return msg;
error:
l_genl_msg_unref(msg);
return NULL;
}
static void netdev_cmd_ft_reassociate_cb(struct l_genl_msg *msg,
void *user_data)
{
@ -2073,197 +1831,6 @@ static void netdev_cmd_ft_reassociate_cb(struct l_genl_msg *msg,
}
}
static void netdev_ft_process_authenticate(struct netdev *netdev,
const uint8_t *frame, size_t frame_len)
{
struct l_genl_msg *cmd_associate, *cmd_deauth;
uint16_t status_code;
const uint8_t *ies = NULL;
size_t ies_len;
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;
/*
* Parse the Authentication Response and validate the contents
* according to 12.5.2 / 12.5.4: RSN or non-RSN Over-the-air
* FT Protocol.
*/
if (!ft_parse_authentication_resp_frame(frame, frame_len,
netdev->addr, hs->aa, hs->aa, 2,
&status_code, &ies, &ies_len))
goto auth_error;
/* AP Rejected the authenticate / associate */
if (status_code != 0)
goto auth_error;
/* Check 802.11r IEs */
if (!ies)
goto ft_error;
ie_tlv_iter_init(&iter, ies, ies_len);
while (ie_tlv_iter_next(&iter)) {
switch (ie_tlv_iter_get_tag(&iter)) {
case IE_TYPE_RSN:
if (rsne)
goto ft_error;
rsne = ie_tlv_iter_get_data(&iter) - 2;
break;
case IE_TYPE_MOBILITY_DOMAIN:
if (mde)
goto ft_error;
mde = ie_tlv_iter_get_data(&iter) - 2;
break;
case IE_TYPE_FAST_BSS_TRANSITION:
if (fte)
goto ft_error;
fte = ie_tlv_iter_get_data(&iter) - 2;
break;
}
}
is_rsn = hs->supplicant_ie != NULL;
/*
* In an RSN, check for an RSNE containing the PMK-R0-Name and
* the remaining fields same as in the advertised RSNE.
*
* 12.8.3: "The RSNE shall be present only if dot11RSNAActivated
* is true. If present, the RSNE shall be set as follows:
* Version field shall be set to 1.
* PMKID Count field shall be set to 1.
* PMKID List field shall be set to the value contained in the
* first message of this sequence.
* All other fields shall be identical to the contents of the
* RSNE advertised by the AP in Beacon and Probe Response frames."
*/
if (is_rsn) {
struct ie_rsn_info msg2_rsne;
if (!rsne)
goto ft_error;
if (ie_parse_rsne_from_data(rsne, rsne[1] + 2,
&msg2_rsne) < 0)
goto ft_error;
if (msg2_rsne.num_pmkids != 1 ||
memcmp(msg2_rsne.pmkids, hs->pmk_r0_name, 16))
goto ft_error;
if (!handshake_util_ap_ie_matches(rsne, hs->authenticator_ie,
false))
goto ft_error;
} else if (rsne)
goto ft_error;
/*
* Check for an MD IE identical to the one we sent in message 1
*
* 12.8.3: "The MDE shall contain the MDID and FT Capability and
* Policy fields. This element shall be the same as the MDE
* advertised by the target AP in Beacon and Probe Response frames."
*/
if (!mde || memcmp(hs->mde, mde, hs->mde[1] + 2))
goto ft_error;
/*
* In an RSN, check for an FT IE with the same R0KH-ID and the same
* SNonce that we sent, and check that the R1KH-ID and the ANonce
* are present. Use them to generate new PMK-R1, PMK-R1-Name and PTK
* in handshake.c.
*
* 12.8.3: "The FTE shall be present only if dot11RSNAActivated is
* true. If present, the FTE shall be set as follows:
* R0KH-ID shall be identical to the R0KH-ID provided by the FTO
* in the first message.
* R1KH-ID shall be set to the R1KH-ID of the target AP, from
* dot11FTR1KeyHolderID.
* ANonce shall be set to a value chosen randomly by the target AP,
* following the recommendations of 11.6.5.
* SNonce shall be set to the value contained in the first message
* of this sequence.
* All other fields shall be set to 0."
*/
if (is_rsn) {
struct ie_ft_info ft_info;
uint8_t zeros[16] = {};
if (!fte)
goto ft_error;
if (ie_parse_fast_bss_transition_from_data(fte, fte[1] + 2,
&ft_info) < 0)
goto ft_error;
if (ft_info.mic_element_count != 0 ||
memcmp(ft_info.mic, zeros, 16))
goto ft_error;
if (hs->r0khid_len != ft_info.r0khid_len ||
memcmp(hs->r0khid, ft_info.r0khid,
hs->r0khid_len) ||
!ft_info.r1khid_present)
goto ft_error;
if (memcmp(ft_info.snonce, hs->snonce, 32))
goto ft_error;
handshake_state_set_fte(hs, fte);
handshake_state_set_anonce(hs, ft_info.anonce);
handshake_state_set_kh_ids(hs, ft_info.r0khid,
ft_info.r0khid_len,
ft_info.r1khid);
handshake_state_derive_ptk(hs);
} else if (fte)
goto ft_error;
cmd_associate = netdev_build_cmd_ft_reassociate(netdev);
if (!cmd_associate)
goto ft_error;
netdev->connect_cmd_id = l_genl_family_send(nl80211,
cmd_associate,
netdev_cmd_ft_reassociate_cb,
netdev, NULL);
if (!netdev->connect_cmd_id) {
l_genl_msg_unref(cmd_associate);
goto ft_error;
}
if (netdev->sm)
eapol_register(netdev->sm); /* See netdev_cmd_connect_cb */
return;
auth_error:
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
status_code);
return;
ft_error:
netdev->result = NETDEV_RESULT_AUTHENTICATION_FAILED;
netdev->last_code = MMPDU_STATUS_CODE_UNSPECIFIED;
cmd_deauth = netdev_build_cmd_deauthenticate(netdev,
MMPDU_REASON_CODE_UNSPECIFIED);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211, cmd_deauth,
netdev_disconnect_cb,
netdev, NULL);
}
static void netdev_authenticate_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
@ -2324,9 +1891,7 @@ static void netdev_authenticate_event(struct l_genl_msg *msg,
if (!frame)
goto auth_error;
if (netdev->in_ft)
netdev_ft_process_authenticate(netdev, frame, frame_len);
else if (netdev->ap) {
if (netdev->ap) {
ret = auth_proto_rx_authenticate(netdev->ap, frame, frame_len);
if (ret == 0 || ret == -EAGAIN)
return;
@ -2401,6 +1966,17 @@ static void netdev_associate_event(struct l_genl_msg *msg,
/* Just in case this was a retry */
netdev->ignore_connect_event = false;
/*
* If in FT we need to prevent the 4-way handshake from
* happening, and instead just wait for rekeys
*/
if (netdev->in_ft) {
eapol_sm_set_require_handshake(netdev->sm,
false);
eapol_sm_set_use_eapol_start(netdev->sm, false);
netdev->in_ft = false;
}
return;
} else if (ret == -EAGAIN) {
/*
@ -2415,22 +1991,6 @@ static void netdev_associate_event(struct l_genl_msg *msg,
goto assoc_failed;
}
if (netdev->in_ft) {
bool is_rsn = netdev->handshake->supplicant_ie != NULL;
if (!netdev_ft_process_associate(netdev, frame, frame_len,
&status_code))
goto assoc_failed;
if (status_code != 0)
goto assoc_failed;
netdev->in_ft = false;
if (is_rsn)
handshake_state_install_ptk(netdev->handshake);
}
return;
assoc_failed:
@ -3081,7 +2641,6 @@ int netdev_leave_adhoc(struct netdev *netdev, netdev_command_cb_t cb,
*/
static struct l_genl_msg *netdev_build_cmd_ft_authenticate(
struct netdev *netdev,
const struct scan_bss *bss,
const struct handshake_state *hs)
{
uint32_t auth_type = NL80211_AUTHTYPE_FT;
@ -3094,10 +2653,9 @@ static struct l_genl_msg *netdev_build_cmd_ft_authenticate(
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, &bss->frequency);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, bss->addr);
l_genl_msg_append_attr(msg, NL80211_ATTR_SSID,
bss->ssid_len, bss->ssid);
4, &netdev->frequency);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, hs->aa);
l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, hs->ssid_len, hs->ssid);
l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_TYPE, 4, &auth_type);
if (is_rsn) {
@ -3134,7 +2692,7 @@ static struct l_genl_msg *netdev_build_cmd_ft_authenticate(
/* The MDE advertised by the BSS must be passed verbatim */
mde[0] = IE_TYPE_MOBILITY_DOMAIN;
mde[1] = 3;
memcpy(mde + 2, bss->mde, 3);
memcpy(mde + 2, hs->mde + 2, 3);
iov[iov_elems].iov_base = mde;
iov[iov_elems].iov_len = 5;
@ -3192,13 +2750,63 @@ static void netdev_cmd_authenticate_ft_cb(struct l_genl_msg *msg,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
static void netdev_ft_tx_authenticate(void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *cmd_authenticate;
cmd_authenticate = netdev_build_cmd_ft_authenticate(netdev,
netdev->handshake);
if (!cmd_authenticate)
goto restore_snonce;
netdev->connect_cmd_id = l_genl_family_send(nl80211,
cmd_authenticate,
netdev_cmd_authenticate_ft_cb,
netdev, NULL);
if (!netdev->connect_cmd_id) {
l_genl_msg_unref(cmd_authenticate);
goto restore_snonce;
}
return;
restore_snonce:
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
static void netdev_ft_tx_associate(struct iovec *ie_iov, size_t iov_len,
void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
msg = netdev_build_cmd_associate_common(netdev);
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN,
netdev->prev_bssid);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, ie_iov, iov_len);
netdev->connect_cmd_id = l_genl_family_send(nl80211, msg,
netdev_cmd_ft_reassociate_cb,
netdev, NULL);
if (!netdev->connect_cmd_id) {
l_genl_msg_unref(msg);
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
}
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_connect_cb_t cb)
{
struct l_genl_msg *cmd_authenticate;
struct netdev_handshake_state *nhs;
uint8_t orig_snonce[32];
int err;
int err = -EINVAL;
if (!netdev->operational)
return -ENOTCONN;
@ -3213,46 +2821,25 @@ int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
* Could also create a new object and copy most of the state but
* we would end up doing more work.
*/
memcpy(netdev->prev_bssid, netdev->handshake->aa, ETH_ALEN);
memcpy(netdev->prev_snonce, netdev->handshake->snonce, 32);
memcpy(orig_snonce, netdev->handshake->snonce, 32);
handshake_state_new_snonce(netdev->handshake);
cmd_authenticate = netdev_build_cmd_ft_authenticate(netdev, target_bss,
netdev->handshake);
if (!cmd_authenticate) {
err = -EINVAL;
goto restore_snonce;
}
netdev->frequency = target_bss->frequency;
netdev->connect_cmd_id = l_genl_family_send(nl80211,
cmd_authenticate,
netdev_cmd_authenticate_ft_cb,
netdev, NULL);
if (!netdev->connect_cmd_id) {
l_genl_msg_unref(cmd_authenticate);
err = -EIO;
goto restore_snonce;
}
memcpy(netdev->prev_bssid, netdev->handshake->aa, ETH_ALEN);
handshake_state_set_authenticator_address(netdev->handshake,
target_bss->addr);
handshake_state_set_authenticator_rsn(netdev->handshake,
target_bss->rsne);
memcpy(netdev->handshake->mde + 2, target_bss->mde, 3);
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = eapol_sm_new(netdev->handshake);
eapol_sm_set_require_handshake(netdev->sm, false);
}
netdev->ap = ft_sm_new(netdev->handshake, netdev_ft_tx_authenticate,
netdev_ft_tx_associate, netdev);
netdev->operational = false;
netdev->in_ft = true;
netdev->connect_cb = cb;
netdev->frequency = target_bss->frequency;
/*
* Cancel commands that could be running because of EAPoL activity
@ -3286,10 +2873,18 @@ int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_rssi_polling_update(netdev);
if (!auth_proto_start(netdev->ap))
goto restore_snonce;
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = NULL;
}
return 0;
restore_snonce:
memcpy(netdev->handshake->snonce, orig_snonce, 32);
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
return err;
}