| /* |
| * Received Data frame processing for TDLS packets |
| * Copyright (c) 2010, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "utils/includes.h" |
| |
| #include "utils/common.h" |
| #include "crypto/sha256.h" |
| #include "crypto/crypto.h" |
| #include "crypto/aes_wrap.h" |
| #include "common/ieee802_11_defs.h" |
| #include "common/ieee802_11_common.h" |
| #include "wlantest.h" |
| |
| |
| static struct wlantest_tdls * get_tdls(struct wlantest *wt, const u8 *linkid, |
| int create_new, const u8 *bssid) |
| { |
| struct wlantest_bss *bss; |
| struct wlantest_sta *init, *resp; |
| struct wlantest_tdls *tdls; |
| |
| bss = bss_find(wt, linkid); |
| if (bss == NULL && bssid) { |
| bss = bss_find(wt, bssid); |
| if (bss) |
| wpa_printf(MSG_INFO, "TDLS: Incorrect BSSID " MACSTR |
| " in LinkId?! (init=" MACSTR " resp=" |
| MACSTR ")", |
| MAC2STR(linkid), MAC2STR(linkid + ETH_ALEN), |
| MAC2STR(linkid + 2 * ETH_ALEN)); |
| } |
| if (bss == NULL) |
| return NULL; |
| |
| init = sta_find(bss, linkid + ETH_ALEN); |
| if (init == NULL) |
| return NULL; |
| |
| resp = sta_find(bss, linkid + 2 * ETH_ALEN); |
| if (resp == NULL) |
| return NULL; |
| |
| dl_list_for_each(tdls, &bss->tdls, struct wlantest_tdls, list) { |
| if (tdls->init == init && tdls->resp == resp) |
| return tdls; |
| } |
| |
| if (!create_new) |
| return NULL; |
| |
| tdls = os_zalloc(sizeof(*tdls)); |
| if (tdls == NULL) |
| return NULL; |
| tdls->init = init; |
| tdls->resp = resp; |
| dl_list_add(&bss->tdls, &tdls->list); |
| return tdls; |
| } |
| |
| |
| static int tdls_derive_tpk(struct wlantest_tdls *tdls, const u8 *bssid, |
| const u8 *ftie, u8 ftie_len) |
| { |
| const struct rsn_ftie *f; |
| u8 key_input[SHA256_MAC_LEN]; |
| const u8 *nonce[2]; |
| size_t len[2]; |
| u8 data[3 * ETH_ALEN]; |
| |
| if (ftie == NULL || ftie_len < sizeof(struct rsn_ftie)) |
| return 0; |
| |
| f = (const struct rsn_ftie *) ftie; |
| wpa_hexdump(MSG_DEBUG, "TDLS ANonce", f->anonce, WPA_NONCE_LEN); |
| wpa_hexdump(MSG_DEBUG, "TDLS SNonce", f->snonce, WPA_NONCE_LEN); |
| |
| /* |
| * IEEE Std 802.11z-2010 8.5.9.1: |
| * TPK-Key-Input = SHA-256(min(SNonce, ANonce) || max(SNonce, ANonce)) |
| */ |
| len[0] = WPA_NONCE_LEN; |
| len[1] = WPA_NONCE_LEN; |
| if (os_memcmp(f->anonce, f->snonce, WPA_NONCE_LEN) < 0) { |
| nonce[0] = f->anonce; |
| nonce[1] = f->snonce; |
| } else { |
| nonce[0] = f->snonce; |
| nonce[1] = f->anonce; |
| } |
| sha256_vector(2, nonce, len, key_input); |
| wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-Key-Input", |
| key_input, SHA256_MAC_LEN); |
| |
| /* |
| * TPK-Key-Data = KDF-N_KEY(TPK-Key-Input, "TDLS PMK", |
| * min(MAC_I, MAC_R) || max(MAC_I, MAC_R) || BSSID || N_KEY) |
| * TODO: is N_KEY really included in KDF Context and if so, in which |
| * presentation format (little endian 16-bit?) is it used? It gets |
| * added by the KDF anyway.. |
| */ |
| |
| if (os_memcmp(tdls->init->addr, tdls->resp->addr, ETH_ALEN) < 0) { |
| os_memcpy(data, tdls->init->addr, ETH_ALEN); |
| os_memcpy(data + ETH_ALEN, tdls->resp->addr, ETH_ALEN); |
| } else { |
| os_memcpy(data, tdls->resp->addr, ETH_ALEN); |
| os_memcpy(data + ETH_ALEN, tdls->init->addr, ETH_ALEN); |
| } |
| os_memcpy(data + 2 * ETH_ALEN, bssid, ETH_ALEN); |
| wpa_hexdump(MSG_DEBUG, "TDLS: KDF Context", data, sizeof(data)); |
| |
| sha256_prf(key_input, SHA256_MAC_LEN, "TDLS PMK", data, sizeof(data), |
| (u8 *) &tdls->tpk, sizeof(tdls->tpk)); |
| wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-KCK", |
| tdls->tpk.kck, sizeof(tdls->tpk.kck)); |
| wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-TK", |
| tdls->tpk.tk, sizeof(tdls->tpk.tk)); |
| |
| return 1; |
| } |
| |
| |
| static int tdls_verify_mic(struct wlantest_tdls *tdls, u8 trans_seq, |
| struct ieee802_11_elems *elems) |
| { |
| u8 *buf, *pos; |
| int len; |
| u8 mic[16]; |
| int ret; |
| const struct rsn_ftie *rx_ftie; |
| struct rsn_ftie *tmp_ftie; |
| |
| if (elems->link_id == NULL || elems->rsn_ie == NULL || |
| elems->timeout_int == NULL || elems->ftie == NULL) |
| return -1; |
| |
| len = 2 * ETH_ALEN + 1 + 2 + 18 + 2 + elems->rsn_ie_len + |
| 2 + elems->timeout_int_len + 2 + elems->ftie_len; |
| |
| buf = os_zalloc(len); |
| if (buf == NULL) |
| return -1; |
| |
| pos = buf; |
| /* 1) TDLS initiator STA MAC address */ |
| os_memcpy(pos, elems->link_id + ETH_ALEN, ETH_ALEN); |
| pos += ETH_ALEN; |
| /* 2) TDLS responder STA MAC address */ |
| os_memcpy(pos, elems->link_id + 2 * ETH_ALEN, ETH_ALEN); |
| pos += ETH_ALEN; |
| /* 3) Transaction Sequence number */ |
| *pos++ = trans_seq; |
| /* 4) Link Identifier IE */ |
| os_memcpy(pos, elems->link_id - 2, 2 + 18); |
| pos += 2 + 18; |
| /* 5) RSN IE */ |
| os_memcpy(pos, elems->rsn_ie - 2, 2 + elems->rsn_ie_len); |
| pos += 2 + elems->rsn_ie_len; |
| /* 6) Timeout Interval IE */ |
| os_memcpy(pos, elems->timeout_int - 2, 2 + elems->timeout_int_len); |
| pos += 2 + elems->timeout_int_len; |
| /* 7) FTIE, with the MIC field of the FTIE set to 0 */ |
| os_memcpy(pos, elems->ftie - 2, 2 + elems->ftie_len); |
| pos += 2; |
| tmp_ftie = (struct rsn_ftie *) pos; |
| os_memset(tmp_ftie->mic, 0, 16); |
| pos += elems->ftie_len; |
| |
| wpa_hexdump(MSG_DEBUG, "TDLS: Data for FTIE MIC", buf, pos - buf); |
| wpa_hexdump_key(MSG_DEBUG, "TDLS: KCK", tdls->tpk.kck, 16); |
| ret = omac1_aes_128(tdls->tpk.kck, buf, pos - buf, mic); |
| os_free(buf); |
| if (ret) |
| return -1; |
| wpa_hexdump(MSG_DEBUG, "TDLS: FTIE MIC", mic, 16); |
| rx_ftie = (const struct rsn_ftie *) elems->ftie; |
| |
| if (os_memcmp(mic, rx_ftie->mic, 16) == 0) { |
| wpa_printf(MSG_DEBUG, "TDLS: Valid MIC"); |
| return 0; |
| } |
| wpa_printf(MSG_DEBUG, "TDLS: Invalid MIC"); |
| return -1; |
| } |
| |
| |
| static void rx_data_tdls_setup_request(struct wlantest *wt, const u8 *bssid, |
| const u8 *sta_addr, const u8 *dst, |
| const u8 *src, |
| const u8 *data, size_t len) |
| { |
| struct ieee802_11_elems elems; |
| struct wlantest_tdls *tdls; |
| |
| if (len < 3) { |
| wpa_printf(MSG_INFO, "Too short TDLS Setup Request " MACSTR |
| " -> " MACSTR, MAC2STR(src), MAC2STR(dst)); |
| return; |
| } |
| wpa_printf(MSG_DEBUG, "TDLS Setup Request " MACSTR " -> " |
| MACSTR, MAC2STR(src), MAC2STR(dst)); |
| |
| if (ieee802_11_parse_elems(data + 3, len - 3, &elems, 1) == |
| ParseFailed || elems.link_id == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR |
| " initiator STA " MACSTR " responder STA " MACSTR, |
| MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), |
| MAC2STR(elems.link_id + 2 * ETH_ALEN)); |
| tdls = get_tdls(wt, elems.link_id, 1, bssid); |
| if (tdls) { |
| tdls->counters[WLANTEST_TDLS_COUNTER_SETUP_REQ]++; |
| tdls->dialog_token = data[0]; |
| } |
| } |
| |
| |
| static void rx_data_tdls_setup_response_failure(struct wlantest *wt, |
| const u8 *bssid, |
| const u8 *sta_addr, |
| u8 dialog_token, u16 status) |
| { |
| struct wlantest_bss *bss; |
| struct wlantest_tdls *tdls; |
| struct wlantest_sta *sta; |
| |
| if (status == WLAN_STATUS_SUCCESS) { |
| wpa_printf(MSG_INFO, "TDLS: Invalid TDLS Setup Response from " |
| MACSTR, MAC2STR(sta_addr)); |
| return; |
| } |
| |
| bss = bss_find(wt, bssid); |
| if (!bss) |
| return; |
| sta = sta_find(bss, sta_addr); |
| if (!sta) |
| return; |
| |
| dl_list_for_each(tdls, &bss->tdls, struct wlantest_tdls, list) { |
| if (tdls->resp == sta) { |
| if (dialog_token != tdls->dialog_token) { |
| wpa_printf(MSG_DEBUG, "TDLS: Dialog token " |
| "mismatch in TDLS Setup Response " |
| "(failure)"); |
| break; |
| } |
| wpa_printf(MSG_DEBUG, "TDLS: Found matching TDLS " |
| "setup session based on dialog token"); |
| tdls->counters[ |
| WLANTEST_TDLS_COUNTER_SETUP_RESP_FAIL]++; |
| break; |
| } |
| } |
| } |
| |
| |
| static void rx_data_tdls_setup_response(struct wlantest *wt, const u8 *bssid, |
| const u8 *sta_addr, const u8 *dst, |
| const u8 *src, |
| const u8 *data, size_t len) |
| { |
| u16 status; |
| struct ieee802_11_elems elems; |
| struct wlantest_tdls *tdls; |
| |
| if (len < 3) { |
| wpa_printf(MSG_INFO, "Too short TDLS Setup Response " MACSTR |
| " -> " MACSTR, MAC2STR(src), MAC2STR(dst)); |
| return; |
| } |
| status = WPA_GET_LE16(data); |
| wpa_printf(MSG_DEBUG, "TDLS Setup Response " MACSTR " -> " |
| MACSTR " (status %d)", |
| MAC2STR(src), MAC2STR(dst), status); |
| if (len < 5 && status == 0) { |
| wpa_printf(MSG_INFO, "Too short TDLS Setup Response " MACSTR |
| " -> " MACSTR, MAC2STR(src), MAC2STR(dst)); |
| return; |
| } |
| |
| if (len < 5 || |
| ieee802_11_parse_elems(data + 5, len - 5, &elems, 1) == |
| ParseFailed || elems.link_id == NULL) { |
| /* Need to match TDLS link based on Dialog Token */ |
| rx_data_tdls_setup_response_failure(wt, bssid, sta_addr, |
| data[2], status); |
| return; |
| } |
| wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR |
| " initiator STA " MACSTR " responder STA " MACSTR, |
| MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), |
| MAC2STR(elems.link_id + 2 * ETH_ALEN)); |
| |
| tdls = get_tdls(wt, elems.link_id, 1, bssid); |
| if (!tdls) |
| return; |
| if (status) |
| tdls->counters[WLANTEST_TDLS_COUNTER_SETUP_RESP_FAIL]++; |
| else |
| tdls->counters[WLANTEST_TDLS_COUNTER_SETUP_RESP_OK]++; |
| |
| if (status != WLAN_STATUS_SUCCESS) |
| return; |
| |
| if (tdls_derive_tpk(tdls, bssid, elems.ftie, elems.ftie_len) < 1) |
| return; |
| if (tdls_verify_mic(tdls, 2, &elems) == 0) { |
| tdls->dialog_token = data[2]; |
| wpa_printf(MSG_DEBUG, "TDLS: Dialog Token for the link: %u", |
| tdls->dialog_token); |
| } |
| } |
| |
| |
| static void rx_data_tdls_setup_confirm_failure(struct wlantest *wt, |
| const u8 *bssid, |
| const u8 *src, |
| u8 dialog_token, u16 status) |
| { |
| struct wlantest_bss *bss; |
| struct wlantest_tdls *tdls; |
| struct wlantest_sta *sta; |
| |
| if (status == WLAN_STATUS_SUCCESS) { |
| wpa_printf(MSG_INFO, "TDLS: Invalid TDLS Setup Confirm from " |
| MACSTR, MAC2STR(src)); |
| return; |
| } |
| |
| bss = bss_find(wt, bssid); |
| if (!bss) |
| return; |
| sta = sta_find(bss, src); |
| if (!sta) |
| return; |
| |
| dl_list_for_each(tdls, &bss->tdls, struct wlantest_tdls, list) { |
| if (tdls->init == sta) { |
| if (dialog_token != tdls->dialog_token) { |
| wpa_printf(MSG_DEBUG, "TDLS: Dialog token " |
| "mismatch in TDLS Setup Confirm " |
| "(failure)"); |
| break; |
| } |
| wpa_printf(MSG_DEBUG, "TDLS: Found matching TDLS " |
| "setup session based on dialog token"); |
| tdls->counters[ |
| WLANTEST_TDLS_COUNTER_SETUP_CONF_FAIL]++; |
| break; |
| } |
| } |
| } |
| |
| |
| static void rx_data_tdls_setup_confirm(struct wlantest *wt, const u8 *bssid, |
| const u8 *sta_addr, const u8 *dst, |
| const u8 *src, |
| const u8 *data, size_t len) |
| { |
| u16 status; |
| struct ieee802_11_elems elems; |
| struct wlantest_tdls *tdls; |
| u8 link_id[3 * ETH_ALEN]; |
| |
| if (len < 3) { |
| wpa_printf(MSG_INFO, "Too short TDLS Setup Confirm " MACSTR |
| " -> " MACSTR, MAC2STR(src), MAC2STR(dst)); |
| return; |
| } |
| status = WPA_GET_LE16(data); |
| wpa_printf(MSG_DEBUG, "TDLS Setup Confirm " MACSTR " -> " |
| MACSTR " (status %d)", |
| MAC2STR(src), MAC2STR(dst), status); |
| |
| if (ieee802_11_parse_elems(data + 3, len - 3, &elems, 1) == |
| ParseFailed || elems.link_id == NULL) { |
| /* Need to match TDLS link based on Dialog Token */ |
| rx_data_tdls_setup_confirm_failure(wt, bssid, src, |
| data[2], status); |
| return; |
| } |
| wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR |
| " initiator STA " MACSTR " responder STA " MACSTR, |
| MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), |
| MAC2STR(elems.link_id + 2 * ETH_ALEN)); |
| |
| tdls = get_tdls(wt, elems.link_id, 1, bssid); |
| if (tdls == NULL) |
| return; |
| if (status) |
| tdls->counters[WLANTEST_TDLS_COUNTER_SETUP_CONF_FAIL]++; |
| else |
| tdls->counters[WLANTEST_TDLS_COUNTER_SETUP_CONF_OK]++; |
| |
| if (status != WLAN_STATUS_SUCCESS) |
| return; |
| |
| tdls->link_up = 1; |
| if (tdls_derive_tpk(tdls, bssid, elems.ftie, elems.ftie_len) < 1) { |
| if (elems.ftie == NULL) |
| goto remove_reverse; |
| return; |
| } |
| if (tdls_verify_mic(tdls, 3, &elems) == 0) { |
| tdls->dialog_token = data[2]; |
| wpa_printf(MSG_DEBUG, "TDLS: Dialog Token for the link: %u", |
| tdls->dialog_token); |
| } |
| |
| remove_reverse: |
| /* |
| * The TDLS link itself is bidirectional, but there is explicit |
| * initiator/responder roles. Remove the other direction of the link |
| * (if it exists) to make sure that the link counters are stored for |
| * the current TDLS entery. |
| */ |
| os_memcpy(link_id, elems.link_id, ETH_ALEN); |
| os_memcpy(link_id + ETH_ALEN, elems.link_id + 2 * ETH_ALEN, ETH_ALEN); |
| os_memcpy(link_id + 2 * ETH_ALEN, elems.link_id + ETH_ALEN, ETH_ALEN); |
| tdls = get_tdls(wt, link_id, 0, bssid); |
| if (tdls) { |
| wpa_printf(MSG_DEBUG, "TDLS: Remove reverse link entry"); |
| tdls_deinit(tdls); |
| } |
| } |
| |
| |
| static int tdls_verify_mic_teardown(struct wlantest_tdls *tdls, u8 trans_seq, |
| const u8 *reason_code, |
| struct ieee802_11_elems *elems) |
| { |
| u8 *buf, *pos; |
| int len; |
| u8 mic[16]; |
| int ret; |
| const struct rsn_ftie *rx_ftie; |
| struct rsn_ftie *tmp_ftie; |
| |
| if (elems->link_id == NULL || elems->ftie == NULL) |
| return -1; |
| |
| len = 2 + 18 + 2 + 1 + 1 + 2 + elems->ftie_len; |
| |
| buf = os_zalloc(len); |
| if (buf == NULL) |
| return -1; |
| |
| pos = buf; |
| /* 1) Link Identifier IE */ |
| os_memcpy(pos, elems->link_id - 2, 2 + 18); |
| pos += 2 + 18; |
| /* 2) Reason Code */ |
| os_memcpy(pos, reason_code, 2); |
| pos += 2; |
| /* 3) Dialog token */ |
| *pos++ = tdls->dialog_token; |
| /* 4) Transaction Sequence number */ |
| *pos++ = trans_seq; |
| /* 5) FTIE, with the MIC field of the FTIE set to 0 */ |
| os_memcpy(pos, elems->ftie - 2, 2 + elems->ftie_len); |
| pos += 2; |
| tmp_ftie = (struct rsn_ftie *) pos; |
| os_memset(tmp_ftie->mic, 0, 16); |
| pos += elems->ftie_len; |
| |
| wpa_hexdump(MSG_DEBUG, "TDLS: Data for FTIE MIC", buf, pos - buf); |
| wpa_hexdump_key(MSG_DEBUG, "TDLS: KCK", tdls->tpk.kck, 16); |
| ret = omac1_aes_128(tdls->tpk.kck, buf, pos - buf, mic); |
| os_free(buf); |
| if (ret) |
| return -1; |
| wpa_hexdump(MSG_DEBUG, "TDLS: FTIE MIC", mic, 16); |
| rx_ftie = (const struct rsn_ftie *) elems->ftie; |
| |
| if (os_memcmp(mic, rx_ftie->mic, 16) == 0) { |
| wpa_printf(MSG_DEBUG, "TDLS: Valid MIC"); |
| return 0; |
| } |
| wpa_printf(MSG_DEBUG, "TDLS: Invalid MIC"); |
| return -1; |
| } |
| |
| |
| static void rx_data_tdls_teardown(struct wlantest *wt, const u8 *bssid, |
| const u8 *sta_addr, const u8 *dst, |
| const u8 *src, |
| const u8 *data, size_t len) |
| { |
| u16 reason; |
| struct ieee802_11_elems elems; |
| struct wlantest_tdls *tdls; |
| |
| if (len < 2) |
| return; |
| reason = WPA_GET_LE16(data); |
| wpa_printf(MSG_DEBUG, "TDLS Teardown " MACSTR " -> " |
| MACSTR " (reason %d)", |
| MAC2STR(src), MAC2STR(dst), reason); |
| |
| if (ieee802_11_parse_elems(data + 2, len - 2, &elems, 1) == |
| ParseFailed || elems.link_id == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "TDLS Link Identifier: BSSID " MACSTR |
| " initiator STA " MACSTR " responder STA " MACSTR, |
| MAC2STR(elems.link_id), MAC2STR(elems.link_id + ETH_ALEN), |
| MAC2STR(elems.link_id + 2 * ETH_ALEN)); |
| |
| tdls = get_tdls(wt, elems.link_id, 1, bssid); |
| if (tdls) { |
| tdls->link_up = 0; |
| tdls->counters[WLANTEST_TDLS_COUNTER_TEARDOWN]++; |
| tdls_verify_mic_teardown(tdls, 4, data, &elems); |
| } |
| } |
| |
| |
| static void rx_data_tdls(struct wlantest *wt, const u8 *bssid, |
| const u8 *sta_addr, const u8 *dst, const u8 *src, |
| const u8 *data, size_t len) |
| { |
| /* data contains the payload of a TDLS Action frame */ |
| if (len < 2 || data[0] != WLAN_ACTION_TDLS) { |
| wpa_hexdump(MSG_DEBUG, "Unrecognized encapsulated TDLS frame", |
| data, len); |
| return; |
| } |
| |
| switch (data[1]) { |
| case WLAN_TDLS_SETUP_REQUEST: |
| rx_data_tdls_setup_request(wt, bssid, sta_addr, dst, src, |
| data + 2, len - 2); |
| break; |
| case WLAN_TDLS_SETUP_RESPONSE: |
| rx_data_tdls_setup_response(wt, bssid, sta_addr, dst, src, |
| data + 2, len - 2); |
| break; |
| case WLAN_TDLS_SETUP_CONFIRM: |
| rx_data_tdls_setup_confirm(wt, bssid, sta_addr, dst, src, |
| data + 2, len - 2); |
| break; |
| case WLAN_TDLS_TEARDOWN: |
| rx_data_tdls_teardown(wt, bssid, sta_addr, dst, src, data + 2, |
| len - 2); |
| break; |
| case WLAN_TDLS_DISCOVERY_REQUEST: |
| wpa_printf(MSG_DEBUG, "TDLS Discovery Request " MACSTR " -> " |
| MACSTR, MAC2STR(src), MAC2STR(dst)); |
| break; |
| } |
| } |
| |
| |
| void rx_data_80211_encap(struct wlantest *wt, const u8 *bssid, |
| const u8 *sta_addr, const u8 *dst, const u8 *src, |
| const u8 *data, size_t len) |
| { |
| wpa_hexdump(MSG_EXCESSIVE, "802.11 data encap frame", data, len); |
| if (len < 1) |
| return; |
| if (data[0] == 0x02) |
| rx_data_tdls(wt, bssid, sta_addr, dst, src, data + 1, len - 1); |
| } |