/* * Received Data frame processing for TDLS packets * Copyright (c) 2010, Jouni Malinen * * 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) add_note(wt, 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; add_note(wt, MSG_DEBUG, "Add new TDLS link context: initiator " MACSTR " responder " MACSTR " BSSID " MACSTR, MAC2STR(linkid + ETH_ALEN), MAC2STR(linkid + 2 * ETH_ALEN), MAC2STR(bssid)); 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 *wt, 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) { add_note(wt, MSG_DEBUG, "TDLS: Valid MIC"); return 0; } add_note(wt, 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; u8 linkid[3 * ETH_ALEN]; if (len < 3) { add_note(wt, 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]; if (elems.ftie && elems.ftie_len >= sizeof(struct rsn_ftie)) { const struct rsn_ftie *f; f = (const struct rsn_ftie *) elems.ftie; os_memcpy(tdls->inonce, f->snonce, WPA_NONCE_LEN); } } /* Check whether reverse direction context exists already */ os_memcpy(linkid, bssid, ETH_ALEN); os_memcpy(linkid + ETH_ALEN, dst, ETH_ALEN); os_memcpy(linkid + 2 * ETH_ALEN, src, ETH_ALEN); tdls = get_tdls(wt, linkid, 0, bssid); if (tdls) add_note(wt, MSG_INFO, "Reverse direction TDLS context exists"); } 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) { add_note(wt, 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) { add_note(wt, MSG_DEBUG, "TDLS: Dialog token " "mismatch in TDLS Setup Response " "(failure)"); break; } add_note(wt, 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) { add_note(wt, 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) { add_note(wt, 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) { add_note(wt, MSG_INFO, "No match TDLS context found"); 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 (elems.ftie && elems.ftie_len >= sizeof(struct rsn_ftie)) { const struct rsn_ftie *f; f = (const struct rsn_ftie *) elems.ftie; if (os_memcmp(tdls->inonce, f->snonce, WPA_NONCE_LEN) != 0) { add_note(wt, MSG_INFO, "Mismatch in TDLS initiator " "nonce"); } os_memcpy(tdls->rnonce, f->anonce, WPA_NONCE_LEN); } if (tdls_derive_tpk(tdls, bssid, elems.ftie, elems.ftie_len) < 1) return; if (tdls_verify_mic(wt, tdls, 2, &elems) == 0) { tdls->dialog_token = data[2]; add_note(wt, 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) { add_note(wt, 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) { add_note(wt, MSG_DEBUG, "TDLS: Dialog token " "mismatch in TDLS Setup Confirm " "(failure)"); break; } add_note(wt, 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) { add_note(wt, 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; if (elems.ftie && elems.ftie_len >= sizeof(struct rsn_ftie)) { const struct rsn_ftie *f; f = (const struct rsn_ftie *) elems.ftie; if (os_memcmp(tdls->inonce, f->snonce, WPA_NONCE_LEN) != 0) { add_note(wt, MSG_INFO, "Mismatch in TDLS initiator " "nonce"); } if (os_memcmp(tdls->rnonce, f->anonce, WPA_NONCE_LEN) != 0) { add_note(wt, MSG_INFO, "Mismatch in TDLS responder " "nonce"); } } 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(wt, tdls, 3, &elems) == 0) { tdls->dialog_token = data[2]; add_note(wt, MSG_DEBUG, "TDLS: Link up - Dialog Token: %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) { add_note(wt, MSG_DEBUG, "TDLS: Remove reverse link entry"); tdls_deinit(tdls); } } static int tdls_verify_mic_teardown(struct wlantest *wt, 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) { add_note(wt, MSG_DEBUG, "TDLS: Valid MIC"); return 0; } add_note(wt, 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) { if (tdls->link_up) add_note(wt, MSG_DEBUG, "TDLS: Link down"); tdls->link_up = 0; tdls->counters[WLANTEST_TDLS_COUNTER_TEARDOWN]++; tdls_verify_mic_teardown(wt, 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); }