From b0f0b2f493c6f397d1fcaa3cfef13eb919f0ec74 Mon Sep 17 00:00:00 2001 From: Christian Mauderer Date: Wed, 8 Nov 2017 14:10:40 +0100 Subject: wpa: Import all files for KRACK patch. --- freebsd/contrib/wpa/src/ap/pmksa_cache_auth.h | 67 + freebsd/contrib/wpa/src/ap/wmm.h | 23 + freebsd/contrib/wpa/src/ap/wpa_auth.c | 3502 ++++++++++++++++++++ freebsd/contrib/wpa/src/ap/wpa_auth.h | 328 ++ freebsd/contrib/wpa/src/ap/wpa_auth_ft.c | 1794 ++++++++++ freebsd/contrib/wpa/src/ap/wpa_auth_i.h | 259 ++ freebsd/contrib/wpa/src/ap/wpa_auth_ie.h | 57 + freebsd/contrib/wpa/src/eapol_auth/eapol_auth_sm.h | 102 + freebsd/contrib/wpa/src/radius/radius.h | 322 ++ freebsd/contrib/wpa/src/rsn_supp/tdls.c | 3010 +++++++++++++++++ freebsd/contrib/wpa/src/utils/bitfield.h | 21 + freebsd/contrib/wpa/wpa_supplicant/wnm_sta.c | 1150 +++++++ 12 files changed, 10635 insertions(+) create mode 100644 freebsd/contrib/wpa/src/ap/pmksa_cache_auth.h create mode 100644 freebsd/contrib/wpa/src/ap/wmm.h create mode 100644 freebsd/contrib/wpa/src/ap/wpa_auth.c create mode 100644 freebsd/contrib/wpa/src/ap/wpa_auth.h create mode 100644 freebsd/contrib/wpa/src/ap/wpa_auth_ft.c create mode 100644 freebsd/contrib/wpa/src/ap/wpa_auth_i.h create mode 100644 freebsd/contrib/wpa/src/ap/wpa_auth_ie.h create mode 100644 freebsd/contrib/wpa/src/eapol_auth/eapol_auth_sm.h create mode 100644 freebsd/contrib/wpa/src/radius/radius.h create mode 100644 freebsd/contrib/wpa/src/rsn_supp/tdls.c create mode 100644 freebsd/contrib/wpa/src/utils/bitfield.h create mode 100644 freebsd/contrib/wpa/wpa_supplicant/wnm_sta.c diff --git a/freebsd/contrib/wpa/src/ap/pmksa_cache_auth.h b/freebsd/contrib/wpa/src/ap/pmksa_cache_auth.h new file mode 100644 index 00000000..8b7be129 --- /dev/null +++ b/freebsd/contrib/wpa/src/ap/pmksa_cache_auth.h @@ -0,0 +1,67 @@ +/* + * hostapd - PMKSA cache for IEEE 802.11i RSN + * Copyright (c) 2004-2008, 2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef PMKSA_CACHE_H +#define PMKSA_CACHE_H + +#include "radius/radius.h" + +/** + * struct rsn_pmksa_cache_entry - PMKSA cache entry + */ +struct rsn_pmksa_cache_entry { + struct rsn_pmksa_cache_entry *next, *hnext; + u8 pmkid[PMKID_LEN]; + u8 pmk[PMK_LEN]; + size_t pmk_len; + os_time_t expiration; + int akmp; /* WPA_KEY_MGMT_* */ + u8 spa[ETH_ALEN]; + + u8 *identity; + size_t identity_len; + struct wpabuf *cui; + struct radius_class_data radius_class; + u8 eap_type_authsrv; + int vlan_id; + int opportunistic; + + u32 acct_multi_session_id_hi; + u32 acct_multi_session_id_lo; +}; + +struct rsn_pmksa_cache; + +struct rsn_pmksa_cache * +pmksa_cache_auth_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry, + void *ctx), void *ctx); +void pmksa_cache_auth_deinit(struct rsn_pmksa_cache *pmksa); +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_get(struct rsn_pmksa_cache *pmksa, + const u8 *spa, const u8 *pmkid); +struct rsn_pmksa_cache_entry * pmksa_cache_get_okc( + struct rsn_pmksa_cache *pmksa, const u8 *spa, const u8 *aa, + const u8 *pmkid); +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa, + const u8 *pmk, size_t pmk_len, + const u8 *kck, size_t kck_len, + const u8 *aa, const u8 *spa, int session_timeout, + struct eapol_state_machine *eapol, int akmp); +struct rsn_pmksa_cache_entry * +pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa, + const struct rsn_pmksa_cache_entry *old_entry, + const u8 *aa, const u8 *pmkid); +void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache_entry *entry, + struct eapol_state_machine *eapol); +void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, + struct rsn_pmksa_cache_entry *entry); +int pmksa_cache_auth_radius_das_disconnect(struct rsn_pmksa_cache *pmksa, + struct radius_das_attrs *attr); + +#endif /* PMKSA_CACHE_H */ diff --git a/freebsd/contrib/wpa/src/ap/wmm.h b/freebsd/contrib/wpa/src/ap/wmm.h new file mode 100644 index 00000000..b70b8636 --- /dev/null +++ b/freebsd/contrib/wpa/src/ap/wmm.h @@ -0,0 +1,23 @@ +/* + * hostapd / WMM (Wi-Fi Multimedia) + * Copyright 2002-2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WME_H +#define WME_H + +struct ieee80211_mgmt; +struct wmm_tspec_element; + +u8 * hostapd_eid_wmm(struct hostapd_data *hapd, u8 *eid); +int hostapd_eid_wmm_valid(struct hostapd_data *hapd, const u8 *eid, + size_t len); +void hostapd_wmm_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len); +int wmm_process_tspec(struct wmm_tspec_element *tspec); + +#endif /* WME_H */ diff --git a/freebsd/contrib/wpa/src/ap/wpa_auth.c b/freebsd/contrib/wpa/src/ap/wpa_auth.c new file mode 100644 index 00000000..ed48bbf6 --- /dev/null +++ b/freebsd/contrib/wpa/src/ap/wpa_auth.c @@ -0,0 +1,3502 @@ +#include + +/* + * IEEE 802.11 RSN / WPA Authenticator + * Copyright (c) 2004-2015, 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 "utils/eloop.h" +#include "utils/state_machine.h" +#include "utils/bitfield.h" +#include "common/ieee802_11_defs.h" +#include "crypto/aes_wrap.h" +#include "crypto/crypto.h" +#include "crypto/sha1.h" +#include "crypto/sha256.h" +#include "crypto/random.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "ap_config.h" +#include "ieee802_11.h" +#include "wpa_auth.h" +#include "pmksa_cache_auth.h" +#include "wpa_auth_i.h" +#include "wpa_auth_ie.h" + +#define STATE_MACHINE_DATA struct wpa_state_machine +#define STATE_MACHINE_DEBUG_PREFIX "WPA" +#define STATE_MACHINE_ADDR sm->addr + + +static void wpa_send_eapol_timeout(void *eloop_ctx, void *timeout_ctx); +static int wpa_sm_step(struct wpa_state_machine *sm); +static int wpa_verify_key_mic(int akmp, struct wpa_ptk *PTK, u8 *data, + size_t data_len); +static void wpa_sm_call_step(void *eloop_ctx, void *timeout_ctx); +static void wpa_group_sm_step(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static void wpa_request_new_ptk(struct wpa_state_machine *sm); +static int wpa_gtk_update(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static int wpa_group_config_group_keys(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce, + const u8 *pmk, struct wpa_ptk *ptk); +static void wpa_group_free(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static void wpa_group_get(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static void wpa_group_put(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); + +static const u32 dot11RSNAConfigGroupUpdateCount = 4; +static const u32 dot11RSNAConfigPairwiseUpdateCount = 4; +static const u32 eapol_key_timeout_first = 100; /* ms */ +static const u32 eapol_key_timeout_subseq = 1000; /* ms */ +static const u32 eapol_key_timeout_first_group = 500; /* ms */ + +/* TODO: make these configurable */ +static const int dot11RSNAConfigPMKLifetime = 43200; +static const int dot11RSNAConfigPMKReauthThreshold = 70; +static const int dot11RSNAConfigSATimeout = 60; + + +static inline int wpa_auth_mic_failure_report( + struct wpa_authenticator *wpa_auth, const u8 *addr) +{ + if (wpa_auth->cb.mic_failure_report) + return wpa_auth->cb.mic_failure_report(wpa_auth->cb.ctx, addr); + return 0; +} + + +static inline void wpa_auth_psk_failure_report( + struct wpa_authenticator *wpa_auth, const u8 *addr) +{ + if (wpa_auth->cb.psk_failure_report) + wpa_auth->cb.psk_failure_report(wpa_auth->cb.ctx, addr); +} + + +static inline void wpa_auth_set_eapol(struct wpa_authenticator *wpa_auth, + const u8 *addr, wpa_eapol_variable var, + int value) +{ + if (wpa_auth->cb.set_eapol) + wpa_auth->cb.set_eapol(wpa_auth->cb.ctx, addr, var, value); +} + + +static inline int wpa_auth_get_eapol(struct wpa_authenticator *wpa_auth, + const u8 *addr, wpa_eapol_variable var) +{ + if (wpa_auth->cb.get_eapol == NULL) + return -1; + return wpa_auth->cb.get_eapol(wpa_auth->cb.ctx, addr, var); +} + + +static inline const u8 * wpa_auth_get_psk(struct wpa_authenticator *wpa_auth, + const u8 *addr, + const u8 *p2p_dev_addr, + const u8 *prev_psk) +{ + if (wpa_auth->cb.get_psk == NULL) + return NULL; + return wpa_auth->cb.get_psk(wpa_auth->cb.ctx, addr, p2p_dev_addr, + prev_psk); +} + + +static inline int wpa_auth_get_msk(struct wpa_authenticator *wpa_auth, + const u8 *addr, u8 *msk, size_t *len) +{ + if (wpa_auth->cb.get_msk == NULL) + return -1; + return wpa_auth->cb.get_msk(wpa_auth->cb.ctx, addr, msk, len); +} + + +static inline int wpa_auth_set_key(struct wpa_authenticator *wpa_auth, + int vlan_id, + enum wpa_alg alg, const u8 *addr, int idx, + u8 *key, size_t key_len) +{ + if (wpa_auth->cb.set_key == NULL) + return -1; + return wpa_auth->cb.set_key(wpa_auth->cb.ctx, vlan_id, alg, addr, idx, + key, key_len); +} + + +static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth, + const u8 *addr, int idx, u8 *seq) +{ + if (wpa_auth->cb.get_seqnum == NULL) + return -1; + return wpa_auth->cb.get_seqnum(wpa_auth->cb.ctx, addr, idx, seq); +} + + +static inline int +wpa_auth_send_eapol(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *data, size_t data_len, int encrypt) +{ + if (wpa_auth->cb.send_eapol == NULL) + return -1; + return wpa_auth->cb.send_eapol(wpa_auth->cb.ctx, addr, data, data_len, + encrypt); +} + + +#ifdef CONFIG_MESH +static inline int wpa_auth_start_ampe(struct wpa_authenticator *wpa_auth, + const u8 *addr) +{ + if (wpa_auth->cb.start_ampe == NULL) + return -1; + return wpa_auth->cb.start_ampe(wpa_auth->cb.ctx, addr); +} +#endif /* CONFIG_MESH */ + + +int wpa_auth_for_each_sta(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_state_machine *sm, void *ctx), + void *cb_ctx) +{ + if (wpa_auth->cb.for_each_sta == NULL) + return 0; + return wpa_auth->cb.for_each_sta(wpa_auth->cb.ctx, cb, cb_ctx); +} + + +int wpa_auth_for_each_auth(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_authenticator *a, void *ctx), + void *cb_ctx) +{ + if (wpa_auth->cb.for_each_auth == NULL) + return 0; + return wpa_auth->cb.for_each_auth(wpa_auth->cb.ctx, cb, cb_ctx); +} + + +void wpa_auth_logger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *txt) +{ + if (wpa_auth->cb.logger == NULL) + return; + wpa_auth->cb.logger(wpa_auth->cb.ctx, addr, level, txt); +} + + +void wpa_auth_vlogger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *fmt, ...) +{ + char *format; + int maxlen; + va_list ap; + + if (wpa_auth->cb.logger == NULL) + return; + + maxlen = os_strlen(fmt) + 100; + format = os_malloc(maxlen); + if (!format) + return; + + va_start(ap, fmt); + vsnprintf(format, maxlen, fmt, ap); + va_end(ap); + + wpa_auth_logger(wpa_auth, addr, level, format); + + os_free(format); +} + + +static void wpa_sta_disconnect(struct wpa_authenticator *wpa_auth, + const u8 *addr) +{ + if (wpa_auth->cb.disconnect == NULL) + return; + wpa_printf(MSG_DEBUG, "wpa_sta_disconnect STA " MACSTR, MAC2STR(addr)); + wpa_auth->cb.disconnect(wpa_auth->cb.ctx, addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); +} + + +static int wpa_use_aes_cmac(struct wpa_state_machine *sm) +{ + int ret = 0; +#ifdef CONFIG_IEEE80211R + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) + ret = 1; +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_IEEE80211W + if (wpa_key_mgmt_sha256(sm->wpa_key_mgmt)) + ret = 1; +#endif /* CONFIG_IEEE80211W */ + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) + ret = 1; + return ret; +} + + +static void wpa_rekey_gmk(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + + if (random_get_bytes(wpa_auth->group->GMK, WPA_GMK_LEN)) { + wpa_printf(MSG_ERROR, "Failed to get random data for WPA " + "initialization."); + } else { + wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "GMK rekeyd"); + wpa_hexdump_key(MSG_DEBUG, "GMK", + wpa_auth->group->GMK, WPA_GMK_LEN); + } + + if (wpa_auth->conf.wpa_gmk_rekey) { + eloop_register_timeout(wpa_auth->conf.wpa_gmk_rekey, 0, + wpa_rekey_gmk, wpa_auth, NULL); + } +} + + +static void wpa_rekey_gtk(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + struct wpa_group *group, *next; + + wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "rekeying GTK"); + group = wpa_auth->group; + while (group) { + wpa_group_get(wpa_auth, group); + + group->GTKReKey = TRUE; + do { + group->changed = FALSE; + wpa_group_sm_step(wpa_auth, group); + } while (group->changed); + + next = group->next; + wpa_group_put(wpa_auth, group); + group = next; + } + + if (wpa_auth->conf.wpa_group_rekey) { + eloop_register_timeout(wpa_auth->conf.wpa_group_rekey, + 0, wpa_rekey_gtk, wpa_auth, NULL); + } +} + + +static void wpa_rekey_ptk(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + struct wpa_state_machine *sm = timeout_ctx; + + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, "rekeying PTK"); + wpa_request_new_ptk(sm); + wpa_sm_step(sm); +} + + +static int wpa_auth_pmksa_clear_cb(struct wpa_state_machine *sm, void *ctx) +{ + if (sm->pmksa == ctx) + sm->pmksa = NULL; + return 0; +} + + +static void wpa_auth_pmksa_free_cb(struct rsn_pmksa_cache_entry *entry, + void *ctx) +{ + struct wpa_authenticator *wpa_auth = ctx; + wpa_auth_for_each_sta(wpa_auth, wpa_auth_pmksa_clear_cb, entry); +} + + +static int wpa_group_init_gmk_and_counter(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + u8 buf[ETH_ALEN + 8 + sizeof(unsigned long)]; + u8 rkey[32]; + unsigned long ptr; + + if (random_get_bytes(group->GMK, WPA_GMK_LEN) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "GMK", group->GMK, WPA_GMK_LEN); + + /* + * Counter = PRF-256(Random number, "Init Counter", + * Local MAC Address || Time) + */ + os_memcpy(buf, wpa_auth->addr, ETH_ALEN); + wpa_get_ntp_timestamp(buf + ETH_ALEN); + ptr = (unsigned long) group; + os_memcpy(buf + ETH_ALEN + 8, &ptr, sizeof(ptr)); + if (random_get_bytes(rkey, sizeof(rkey)) < 0) + return -1; + + if (sha1_prf(rkey, sizeof(rkey), "Init Counter", buf, sizeof(buf), + group->Counter, WPA_NONCE_LEN) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "Key Counter", + group->Counter, WPA_NONCE_LEN); + + return 0; +} + + +static struct wpa_group * wpa_group_init(struct wpa_authenticator *wpa_auth, + int vlan_id, int delay_init) +{ + struct wpa_group *group; + + group = os_zalloc(sizeof(struct wpa_group)); + if (group == NULL) + return NULL; + + group->GTKAuthenticator = TRUE; + group->vlan_id = vlan_id; + group->GTK_len = wpa_cipher_key_len(wpa_auth->conf.wpa_group); + + if (random_pool_ready() != 1) { + wpa_printf(MSG_INFO, "WPA: Not enough entropy in random pool " + "for secure operations - update keys later when " + "the first station connects"); + } + + /* + * Set initial GMK/Counter value here. The actual values that will be + * used in negotiations will be set once the first station tries to + * connect. This allows more time for collecting additional randomness + * on embedded devices. + */ + if (wpa_group_init_gmk_and_counter(wpa_auth, group) < 0) { + wpa_printf(MSG_ERROR, "Failed to get random data for WPA " + "initialization."); + os_free(group); + return NULL; + } + + group->GInit = TRUE; + if (delay_init) { + wpa_printf(MSG_DEBUG, "WPA: Delay group state machine start " + "until Beacon frames have been configured"); + /* Initialization is completed in wpa_init_keys(). */ + } else { + wpa_group_sm_step(wpa_auth, group); + group->GInit = FALSE; + wpa_group_sm_step(wpa_auth, group); + } + + return group; +} + + +/** + * wpa_init - Initialize WPA authenticator + * @addr: Authenticator address + * @conf: Configuration for WPA authenticator + * @cb: Callback functions for WPA authenticator + * Returns: Pointer to WPA authenticator data or %NULL on failure + */ +struct wpa_authenticator * wpa_init(const u8 *addr, + struct wpa_auth_config *conf, + struct wpa_auth_callbacks *cb) +{ + struct wpa_authenticator *wpa_auth; + + wpa_auth = os_zalloc(sizeof(struct wpa_authenticator)); + if (wpa_auth == NULL) + return NULL; + os_memcpy(wpa_auth->addr, addr, ETH_ALEN); + os_memcpy(&wpa_auth->conf, conf, sizeof(*conf)); + os_memcpy(&wpa_auth->cb, cb, sizeof(*cb)); + + if (wpa_auth_gen_wpa_ie(wpa_auth)) { + wpa_printf(MSG_ERROR, "Could not generate WPA IE."); + os_free(wpa_auth); + return NULL; + } + + wpa_auth->group = wpa_group_init(wpa_auth, 0, 1); + if (wpa_auth->group == NULL) { + os_free(wpa_auth->wpa_ie); + os_free(wpa_auth); + return NULL; + } + + wpa_auth->pmksa = pmksa_cache_auth_init(wpa_auth_pmksa_free_cb, + wpa_auth); + if (wpa_auth->pmksa == NULL) { + wpa_printf(MSG_ERROR, "PMKSA cache initialization failed."); + os_free(wpa_auth->group); + os_free(wpa_auth->wpa_ie); + os_free(wpa_auth); + return NULL; + } + +#ifdef CONFIG_IEEE80211R + wpa_auth->ft_pmk_cache = wpa_ft_pmk_cache_init(); + if (wpa_auth->ft_pmk_cache == NULL) { + wpa_printf(MSG_ERROR, "FT PMK cache initialization failed."); + os_free(wpa_auth->group); + os_free(wpa_auth->wpa_ie); + pmksa_cache_auth_deinit(wpa_auth->pmksa); + os_free(wpa_auth); + return NULL; + } +#endif /* CONFIG_IEEE80211R */ + + if (wpa_auth->conf.wpa_gmk_rekey) { + eloop_register_timeout(wpa_auth->conf.wpa_gmk_rekey, 0, + wpa_rekey_gmk, wpa_auth, NULL); + } + + if (wpa_auth->conf.wpa_group_rekey) { + eloop_register_timeout(wpa_auth->conf.wpa_group_rekey, 0, + wpa_rekey_gtk, wpa_auth, NULL); + } + +#ifdef CONFIG_P2P + if (WPA_GET_BE32(conf->ip_addr_start)) { + int count = WPA_GET_BE32(conf->ip_addr_end) - + WPA_GET_BE32(conf->ip_addr_start) + 1; + if (count > 1000) + count = 1000; + if (count > 0) + wpa_auth->ip_pool = bitfield_alloc(count); + } +#endif /* CONFIG_P2P */ + + return wpa_auth; +} + + +int wpa_init_keys(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group = wpa_auth->group; + + wpa_printf(MSG_DEBUG, "WPA: Start group state machine to set initial " + "keys"); + wpa_group_sm_step(wpa_auth, group); + group->GInit = FALSE; + wpa_group_sm_step(wpa_auth, group); + if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + return -1; + return 0; +} + + +/** + * wpa_deinit - Deinitialize WPA authenticator + * @wpa_auth: Pointer to WPA authenticator data from wpa_init() + */ +void wpa_deinit(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group, *prev; + + eloop_cancel_timeout(wpa_rekey_gmk, wpa_auth, NULL); + eloop_cancel_timeout(wpa_rekey_gtk, wpa_auth, NULL); + +#ifdef CONFIG_PEERKEY + while (wpa_auth->stsl_negotiations) + wpa_stsl_remove(wpa_auth, wpa_auth->stsl_negotiations); +#endif /* CONFIG_PEERKEY */ + + pmksa_cache_auth_deinit(wpa_auth->pmksa); + +#ifdef CONFIG_IEEE80211R + wpa_ft_pmk_cache_deinit(wpa_auth->ft_pmk_cache); + wpa_auth->ft_pmk_cache = NULL; +#endif /* CONFIG_IEEE80211R */ + +#ifdef CONFIG_P2P + bitfield_free(wpa_auth->ip_pool); +#endif /* CONFIG_P2P */ + + + os_free(wpa_auth->wpa_ie); + + group = wpa_auth->group; + while (group) { + prev = group; + group = group->next; + os_free(prev); + } + + os_free(wpa_auth); +} + + +/** + * wpa_reconfig - Update WPA authenticator configuration + * @wpa_auth: Pointer to WPA authenticator data from wpa_init() + * @conf: Configuration for WPA authenticator + */ +int wpa_reconfig(struct wpa_authenticator *wpa_auth, + struct wpa_auth_config *conf) +{ + struct wpa_group *group; + if (wpa_auth == NULL) + return 0; + + os_memcpy(&wpa_auth->conf, conf, sizeof(*conf)); + if (wpa_auth_gen_wpa_ie(wpa_auth)) { + wpa_printf(MSG_ERROR, "Could not generate WPA IE."); + return -1; + } + + /* + * Reinitialize GTK to make sure it is suitable for the new + * configuration. + */ + group = wpa_auth->group; + group->GTK_len = wpa_cipher_key_len(wpa_auth->conf.wpa_group); + group->GInit = TRUE; + wpa_group_sm_step(wpa_auth, group); + group->GInit = FALSE; + wpa_group_sm_step(wpa_auth, group); + + return 0; +} + + +struct wpa_state_machine * +wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *p2p_dev_addr) +{ + struct wpa_state_machine *sm; + + if (wpa_auth->group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + return NULL; + + sm = os_zalloc(sizeof(struct wpa_state_machine)); + if (sm == NULL) + return NULL; + os_memcpy(sm->addr, addr, ETH_ALEN); + if (p2p_dev_addr) + os_memcpy(sm->p2p_dev_addr, p2p_dev_addr, ETH_ALEN); + + sm->wpa_auth = wpa_auth; + sm->group = wpa_auth->group; + wpa_group_get(sm->wpa_auth, sm->group); + + return sm; +} + + +int wpa_auth_sta_associated(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm) +{ + if (wpa_auth == NULL || !wpa_auth->conf.wpa || sm == NULL) + return -1; + +#ifdef CONFIG_IEEE80211R + if (sm->ft_completed) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, + "FT authentication already completed - do not " + "start 4-way handshake"); + /* Go to PTKINITDONE state to allow GTK rekeying */ + sm->wpa_ptk_state = WPA_PTK_PTKINITDONE; + return 0; + } +#endif /* CONFIG_IEEE80211R */ + + if (sm->started) { + os_memset(&sm->key_replay, 0, sizeof(sm->key_replay)); + sm->ReAuthenticationRequest = TRUE; + return wpa_sm_step(sm); + } + + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, + "start authentication"); + sm->started = 1; + + sm->Init = TRUE; + if (wpa_sm_step(sm) == 1) + return 1; /* should not really happen */ + sm->Init = FALSE; + sm->AuthenticationRequest = TRUE; + return wpa_sm_step(sm); +} + + +void wpa_auth_sta_no_wpa(struct wpa_state_machine *sm) +{ + /* WPA/RSN was not used - clear WPA state. This is needed if the STA + * reassociates back to the same AP while the previous entry for the + * STA has not yet been removed. */ + if (sm == NULL) + return; + + sm->wpa_key_mgmt = 0; +} + + +static void wpa_free_sta_sm(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_P2P + if (WPA_GET_BE32(sm->ip_addr)) { + u32 start; + wpa_printf(MSG_DEBUG, "P2P: Free assigned IP " + "address %u.%u.%u.%u from " MACSTR, + sm->ip_addr[0], sm->ip_addr[1], + sm->ip_addr[2], sm->ip_addr[3], + MAC2STR(sm->addr)); + start = WPA_GET_BE32(sm->wpa_auth->conf.ip_addr_start); + bitfield_clear(sm->wpa_auth->ip_pool, + WPA_GET_BE32(sm->ip_addr) - start); + } +#endif /* CONFIG_P2P */ + if (sm->GUpdateStationKeys) { + sm->group->GKeyDoneStations--; + sm->GUpdateStationKeys = FALSE; + } +#ifdef CONFIG_IEEE80211R + os_free(sm->assoc_resp_ftie); + wpabuf_free(sm->ft_pending_req_ies); +#endif /* CONFIG_IEEE80211R */ + os_free(sm->last_rx_eapol_key); + os_free(sm->wpa_ie); + wpa_group_put(sm->wpa_auth, sm->group); + os_free(sm); +} + + +void wpa_auth_sta_deinit(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return; + + if (sm->wpa_auth->conf.wpa_strict_rekey && sm->has_GTK) { + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "strict rekeying - force GTK rekey since STA " + "is leaving"); + eloop_cancel_timeout(wpa_rekey_gtk, sm->wpa_auth, NULL); + eloop_register_timeout(0, 500000, wpa_rekey_gtk, sm->wpa_auth, + NULL); + } + + eloop_cancel_timeout(wpa_send_eapol_timeout, sm->wpa_auth, sm); + sm->pending_1_of_4_timeout = 0; + eloop_cancel_timeout(wpa_sm_call_step, sm, NULL); + eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm); + if (sm->in_step_loop) { + /* Must not free state machine while wpa_sm_step() is running. + * Freeing will be completed in the end of wpa_sm_step(). */ + wpa_printf(MSG_DEBUG, "WPA: Registering pending STA state " + "machine deinit for " MACSTR, MAC2STR(sm->addr)); + sm->pending_deinit = 1; + } else + wpa_free_sta_sm(sm); +} + + +static void wpa_request_new_ptk(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return; + + sm->PTKRequest = TRUE; + sm->PTK_valid = 0; +} + + +static int wpa_replay_counter_valid(struct wpa_key_replay_counter *ctr, + const u8 *replay_counter) +{ + int i; + for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) { + if (!ctr[i].valid) + break; + if (os_memcmp(replay_counter, ctr[i].counter, + WPA_REPLAY_COUNTER_LEN) == 0) + return 1; + } + return 0; +} + + +static void wpa_replay_counter_mark_invalid(struct wpa_key_replay_counter *ctr, + const u8 *replay_counter) +{ + int i; + for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) { + if (ctr[i].valid && + (replay_counter == NULL || + os_memcmp(replay_counter, ctr[i].counter, + WPA_REPLAY_COUNTER_LEN) == 0)) + ctr[i].valid = FALSE; + } +} + + +#ifdef CONFIG_IEEE80211R +static int ft_check_msg_2_of_4(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + struct wpa_eapol_ie_parse *kde) +{ + struct wpa_ie_data ie; + struct rsn_mdie *mdie; + + if (wpa_parse_wpa_ie_rsn(kde->rsn_ie, kde->rsn_ie_len, &ie) < 0 || + ie.num_pmkid != 1 || ie.pmkid == NULL) { + wpa_printf(MSG_DEBUG, "FT: No PMKR1Name in " + "FT 4-way handshake message 2/4"); + return -1; + } + + os_memcpy(sm->sup_pmk_r1_name, ie.pmkid, PMKID_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name from Supplicant", + sm->sup_pmk_r1_name, PMKID_LEN); + + if (!kde->mdie || !kde->ftie) { + wpa_printf(MSG_DEBUG, "FT: No %s in FT 4-way handshake " + "message 2/4", kde->mdie ? "FTIE" : "MDIE"); + return -1; + } + + mdie = (struct rsn_mdie *) (kde->mdie + 2); + if (kde->mdie[1] < sizeof(struct rsn_mdie) || + os_memcmp(wpa_auth->conf.mobility_domain, mdie->mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: MDIE mismatch"); + return -1; + } + + if (sm->assoc_resp_ftie && + (kde->ftie[1] != sm->assoc_resp_ftie[1] || + os_memcmp(kde->ftie, sm->assoc_resp_ftie, + 2 + sm->assoc_resp_ftie[1]) != 0)) { + wpa_printf(MSG_DEBUG, "FT: FTIE mismatch"); + wpa_hexdump(MSG_DEBUG, "FT: FTIE in EAPOL-Key msg 2/4", + kde->ftie, kde->ftie_len); + wpa_hexdump(MSG_DEBUG, "FT: FTIE in (Re)AssocResp", + sm->assoc_resp_ftie, 2 + sm->assoc_resp_ftie[1]); + return -1; + } + + return 0; +} +#endif /* CONFIG_IEEE80211R */ + + +static int wpa_receive_error_report(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int group) +{ + /* Supplicant reported a Michael MIC error */ + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key Error Request " + "(STA detected Michael MIC failure (group=%d))", + group); + + if (group && wpa_auth->conf.wpa_group != WPA_CIPHER_TKIP) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "ignore Michael MIC failure report since " + "group cipher is not TKIP"); + } else if (!group && sm->pairwise != WPA_CIPHER_TKIP) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "ignore Michael MIC failure report since " + "pairwise cipher is not TKIP"); + } else { + if (wpa_auth_mic_failure_report(wpa_auth, sm->addr) > 0) + return 1; /* STA entry was removed */ + sm->dot11RSNAStatsTKIPRemoteMICFailures++; + wpa_auth->dot11RSNAStatsTKIPRemoteMICFailures++; + } + + /* + * Error report is not a request for a new key handshake, but since + * Authenticator may do it, let's change the keys now anyway. + */ + wpa_request_new_ptk(sm); + return 0; +} + + +static int wpa_try_alt_snonce(struct wpa_state_machine *sm, u8 *data, + size_t data_len) +{ + struct wpa_ptk PTK; + int ok = 0; + const u8 *pmk = NULL; + + for (;;) { + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) { + pmk = wpa_auth_get_psk(sm->wpa_auth, sm->addr, + sm->p2p_dev_addr, pmk); + if (pmk == NULL) + break; + } else + pmk = sm->PMK; + + wpa_derive_ptk(sm, sm->alt_SNonce, pmk, &PTK); + + if (wpa_verify_key_mic(sm->wpa_key_mgmt, &PTK, data, data_len) + == 0) { + ok = 1; + break; + } + + if (!wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) + break; + } + + if (!ok) { + wpa_printf(MSG_DEBUG, + "WPA: Earlier SNonce did not result in matching MIC"); + return -1; + } + + wpa_printf(MSG_DEBUG, + "WPA: Earlier SNonce resulted in matching MIC"); + sm->alt_snonce_valid = 0; + os_memcpy(sm->SNonce, sm->alt_SNonce, WPA_NONCE_LEN); + os_memcpy(&sm->PTK, &PTK, sizeof(PTK)); + sm->PTK_valid = TRUE; + + return 0; +} + + +void wpa_receive(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + u8 *data, size_t data_len) +{ + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + struct wpa_eapol_key_192 *key192; + u16 key_info, key_data_length; + enum { PAIRWISE_2, PAIRWISE_4, GROUP_2, REQUEST, + SMK_M1, SMK_M3, SMK_ERROR } msg; + char *msgtxt; + struct wpa_eapol_ie_parse kde; + int ft; + const u8 *eapol_key_ie, *key_data; + size_t eapol_key_ie_len, keyhdrlen, mic_len; + + if (wpa_auth == NULL || !wpa_auth->conf.wpa || sm == NULL) + return; + + mic_len = wpa_mic_len(sm->wpa_key_mgmt); + keyhdrlen = mic_len == 24 ? sizeof(*key192) : sizeof(*key); + + if (data_len < sizeof(*hdr) + keyhdrlen) + return; + + hdr = (struct ieee802_1x_hdr *) data; + key = (struct wpa_eapol_key *) (hdr + 1); + key192 = (struct wpa_eapol_key_192 *) (hdr + 1); + key_info = WPA_GET_BE16(key->key_info); + if (mic_len == 24) { + key_data = (const u8 *) (key192 + 1); + key_data_length = WPA_GET_BE16(key192->key_data_length); + } else { + key_data = (const u8 *) (key + 1); + key_data_length = WPA_GET_BE16(key->key_data_length); + } + wpa_printf(MSG_DEBUG, "WPA: Received EAPOL-Key from " MACSTR + " key_info=0x%x type=%u key_data_length=%u", + MAC2STR(sm->addr), key_info, key->type, key_data_length); + if (key_data_length > data_len - sizeof(*hdr) - keyhdrlen) { + wpa_printf(MSG_INFO, "WPA: Invalid EAPOL-Key frame - " + "key_data overflow (%d > %lu)", + key_data_length, + (unsigned long) (data_len - sizeof(*hdr) - + keyhdrlen)); + return; + } + + if (sm->wpa == WPA_VERSION_WPA2) { + if (key->type == EAPOL_KEY_TYPE_WPA) { + /* + * Some deployed station implementations seem to send + * msg 4/4 with incorrect type value in WPA2 mode. + */ + wpa_printf(MSG_DEBUG, "Workaround: Allow EAPOL-Key " + "with unexpected WPA type in RSN mode"); + } else if (key->type != EAPOL_KEY_TYPE_RSN) { + wpa_printf(MSG_DEBUG, "Ignore EAPOL-Key with " + "unexpected type %d in RSN mode", + key->type); + return; + } + } else { + if (key->type != EAPOL_KEY_TYPE_WPA) { + wpa_printf(MSG_DEBUG, "Ignore EAPOL-Key with " + "unexpected type %d in WPA mode", + key->type); + return; + } + } + + wpa_hexdump(MSG_DEBUG, "WPA: Received Key Nonce", key->key_nonce, + WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPA: Received Replay Counter", + key->replay_counter, WPA_REPLAY_COUNTER_LEN); + + /* FIX: verify that the EAPOL-Key frame was encrypted if pairwise keys + * are set */ + + if ((key_info & (WPA_KEY_INFO_SMK_MESSAGE | WPA_KEY_INFO_REQUEST)) == + (WPA_KEY_INFO_SMK_MESSAGE | WPA_KEY_INFO_REQUEST)) { + if (key_info & WPA_KEY_INFO_ERROR) { + msg = SMK_ERROR; + msgtxt = "SMK Error"; + } else { + msg = SMK_M1; + msgtxt = "SMK M1"; + } + } else if (key_info & WPA_KEY_INFO_SMK_MESSAGE) { + msg = SMK_M3; + msgtxt = "SMK M3"; + } else if (key_info & WPA_KEY_INFO_REQUEST) { + msg = REQUEST; + msgtxt = "Request"; + } else if (!(key_info & WPA_KEY_INFO_KEY_TYPE)) { + msg = GROUP_2; + msgtxt = "2/2 Group"; + } else if (key_data_length == 0) { + msg = PAIRWISE_4; + msgtxt = "4/4 Pairwise"; + } else { + msg = PAIRWISE_2; + msgtxt = "2/4 Pairwise"; + } + + /* TODO: key_info type validation for PeerKey */ + if (msg == REQUEST || msg == PAIRWISE_2 || msg == PAIRWISE_4 || + msg == GROUP_2) { + u16 ver = key_info & WPA_KEY_INFO_TYPE_MASK; + if (sm->pairwise == WPA_CIPHER_CCMP || + sm->pairwise == WPA_CIPHER_GCMP) { + if (wpa_use_aes_cmac(sm) && + sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN && + !wpa_key_mgmt_suite_b(sm->wpa_key_mgmt) && + ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) { + wpa_auth_logger(wpa_auth, sm->addr, + LOGGER_WARNING, + "advertised support for " + "AES-128-CMAC, but did not " + "use it"); + return; + } + + if (!wpa_use_aes_cmac(sm) && + ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) { + wpa_auth_logger(wpa_auth, sm->addr, + LOGGER_WARNING, + "did not use HMAC-SHA1-AES " + "with CCMP/GCMP"); + return; + } + } + + if (wpa_key_mgmt_suite_b(sm->wpa_key_mgmt) && + ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_WARNING, + "did not use EAPOL-Key descriptor version 0 as required for AKM-defined cases"); + return; + } + } + + if (key_info & WPA_KEY_INFO_REQUEST) { + if (sm->req_replay_counter_used && + os_memcmp(key->replay_counter, sm->req_replay_counter, + WPA_REPLAY_COUNTER_LEN) <= 0) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_WARNING, + "received EAPOL-Key request with " + "replayed counter"); + return; + } + } + + if (!(key_info & WPA_KEY_INFO_REQUEST) && + !wpa_replay_counter_valid(sm->key_replay, key->replay_counter)) { + int i; + + if (msg == PAIRWISE_2 && + wpa_replay_counter_valid(sm->prev_key_replay, + key->replay_counter) && + sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING && + os_memcmp(sm->SNonce, key->key_nonce, WPA_NONCE_LEN) != 0) + { + /* + * Some supplicant implementations (e.g., Windows XP + * WZC) update SNonce for each EAPOL-Key 2/4. This + * breaks the workaround on accepting any of the + * pending requests, so allow the SNonce to be updated + * even if we have already sent out EAPOL-Key 3/4. + */ + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "Process SNonce update from STA " + "based on retransmitted EAPOL-Key " + "1/4"); + sm->update_snonce = 1; + os_memcpy(sm->alt_SNonce, sm->SNonce, WPA_NONCE_LEN); + sm->alt_snonce_valid = TRUE; + os_memcpy(sm->alt_replay_counter, + sm->key_replay[0].counter, + WPA_REPLAY_COUNTER_LEN); + goto continue_processing; + } + + if (msg == PAIRWISE_4 && sm->alt_snonce_valid && + sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING && + os_memcmp(key->replay_counter, sm->alt_replay_counter, + WPA_REPLAY_COUNTER_LEN) == 0) { + /* + * Supplicant may still be using the old SNonce since + * there was two EAPOL-Key 2/4 messages and they had + * different SNonce values. + */ + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "Try to process received EAPOL-Key 4/4 based on old Replay Counter and SNonce from an earlier EAPOL-Key 1/4"); + goto continue_processing; + } + + if (msg == PAIRWISE_2 && + wpa_replay_counter_valid(sm->prev_key_replay, + key->replay_counter) && + sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "ignore retransmitted EAPOL-Key %s - " + "SNonce did not change", msgtxt); + } else { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "received EAPOL-Key %s with " + "unexpected replay counter", msgtxt); + } + for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) { + if (!sm->key_replay[i].valid) + break; + wpa_hexdump(MSG_DEBUG, "pending replay counter", + sm->key_replay[i].counter, + WPA_REPLAY_COUNTER_LEN); + } + wpa_hexdump(MSG_DEBUG, "received replay counter", + key->replay_counter, WPA_REPLAY_COUNTER_LEN); + return; + } + +continue_processing: + switch (msg) { + case PAIRWISE_2: + if (sm->wpa_ptk_state != WPA_PTK_PTKSTART && + sm->wpa_ptk_state != WPA_PTK_PTKCALCNEGOTIATING && + (!sm->update_snonce || + sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING)) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key msg 2/4 in " + "invalid state (%d) - dropped", + sm->wpa_ptk_state); + return; + } + random_add_randomness(key->key_nonce, WPA_NONCE_LEN); + if (sm->group->reject_4way_hs_for_entropy) { + /* + * The system did not have enough entropy to generate + * strong random numbers. Reject the first 4-way + * handshake(s) and collect some entropy based on the + * information from it. Once enough entropy is + * available, the next atempt will trigger GMK/Key + * Counter update and the station will be allowed to + * continue. + */ + wpa_printf(MSG_DEBUG, "WPA: Reject 4-way handshake to " + "collect more entropy for random number " + "generation"); + random_mark_pool_ready(); + wpa_sta_disconnect(wpa_auth, sm->addr); + return; + } + if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key msg 2/4 with " + "invalid Key Data contents"); + return; + } + if (kde.rsn_ie) { + eapol_key_ie = kde.rsn_ie; + eapol_key_ie_len = kde.rsn_ie_len; + } else if (kde.osen) { + eapol_key_ie = kde.osen; + eapol_key_ie_len = kde.osen_len; + } else { + eapol_key_ie = kde.wpa_ie; + eapol_key_ie_len = kde.wpa_ie_len; + } + ft = sm->wpa == WPA_VERSION_WPA2 && + wpa_key_mgmt_ft(sm->wpa_key_mgmt); + if (sm->wpa_ie == NULL || + wpa_compare_rsn_ie(ft, + sm->wpa_ie, sm->wpa_ie_len, + eapol_key_ie, eapol_key_ie_len)) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "WPA IE from (Re)AssocReq did not " + "match with msg 2/4"); + if (sm->wpa_ie) { + wpa_hexdump(MSG_DEBUG, "WPA IE in AssocReq", + sm->wpa_ie, sm->wpa_ie_len); + } + wpa_hexdump(MSG_DEBUG, "WPA IE in msg 2/4", + eapol_key_ie, eapol_key_ie_len); + /* MLME-DEAUTHENTICATE.request */ + wpa_sta_disconnect(wpa_auth, sm->addr); + return; + } +#ifdef CONFIG_IEEE80211R + if (ft && ft_check_msg_2_of_4(wpa_auth, sm, &kde) < 0) { + wpa_sta_disconnect(wpa_auth, sm->addr); + return; + } +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_P2P + if (kde.ip_addr_req && kde.ip_addr_req[0] && + wpa_auth->ip_pool && WPA_GET_BE32(sm->ip_addr) == 0) { + int idx; + wpa_printf(MSG_DEBUG, "P2P: IP address requested in " + "EAPOL-Key exchange"); + idx = bitfield_get_first_zero(wpa_auth->ip_pool); + if (idx >= 0) { + u32 start = WPA_GET_BE32(wpa_auth->conf. + ip_addr_start); + bitfield_set(wpa_auth->ip_pool, idx); + WPA_PUT_BE32(sm->ip_addr, start + idx); + wpa_printf(MSG_DEBUG, "P2P: Assigned IP " + "address %u.%u.%u.%u to " MACSTR, + sm->ip_addr[0], sm->ip_addr[1], + sm->ip_addr[2], sm->ip_addr[3], + MAC2STR(sm->addr)); + } + } +#endif /* CONFIG_P2P */ + break; + case PAIRWISE_4: + if (sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING || + !sm->PTK_valid) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key msg 4/4 in " + "invalid state (%d) - dropped", + sm->wpa_ptk_state); + return; + } + break; + case GROUP_2: + if (sm->wpa_ptk_group_state != WPA_PTK_GROUP_REKEYNEGOTIATING + || !sm->PTK_valid) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key msg 2/2 in " + "invalid state (%d) - dropped", + sm->wpa_ptk_group_state); + return; + } + break; +#ifdef CONFIG_PEERKEY + case SMK_M1: + case SMK_M3: + case SMK_ERROR: + if (!wpa_auth->conf.peerkey) { + wpa_printf(MSG_DEBUG, "RSN: SMK M1/M3/Error, but " + "PeerKey use disabled - ignoring message"); + return; + } + if (!sm->PTK_valid) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key msg SMK in " + "invalid state - dropped"); + return; + } + break; +#else /* CONFIG_PEERKEY */ + case SMK_M1: + case SMK_M3: + case SMK_ERROR: + return; /* STSL disabled - ignore SMK messages */ +#endif /* CONFIG_PEERKEY */ + case REQUEST: + break; + } + + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "received EAPOL-Key frame (%s)", msgtxt); + + if (key_info & WPA_KEY_INFO_ACK) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "received invalid EAPOL-Key: Key Ack set"); + return; + } + + if (!(key_info & WPA_KEY_INFO_MIC)) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "received invalid EAPOL-Key: Key MIC not set"); + return; + } + + sm->MICVerified = FALSE; + if (sm->PTK_valid && !sm->update_snonce) { + if (wpa_verify_key_mic(sm->wpa_key_mgmt, &sm->PTK, data, + data_len) && + (msg != PAIRWISE_4 || !sm->alt_snonce_valid || + wpa_try_alt_snonce(sm, data, data_len))) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key with invalid MIC"); + return; + } + sm->MICVerified = TRUE; + eloop_cancel_timeout(wpa_send_eapol_timeout, wpa_auth, sm); + sm->pending_1_of_4_timeout = 0; + } + + if (key_info & WPA_KEY_INFO_REQUEST) { + if (sm->MICVerified) { + sm->req_replay_counter_used = 1; + os_memcpy(sm->req_replay_counter, key->replay_counter, + WPA_REPLAY_COUNTER_LEN); + } else { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key request with " + "invalid MIC"); + return; + } + + /* + * TODO: should decrypt key data field if encryption was used; + * even though MAC address KDE is not normally encrypted, + * supplicant is allowed to encrypt it. + */ + if (msg == SMK_ERROR) { +#ifdef CONFIG_PEERKEY + wpa_smk_error(wpa_auth, sm, key_data, key_data_length); +#endif /* CONFIG_PEERKEY */ + return; + } else if (key_info & WPA_KEY_INFO_ERROR) { + if (wpa_receive_error_report( + wpa_auth, sm, + !(key_info & WPA_KEY_INFO_KEY_TYPE)) > 0) + return; /* STA entry was removed */ + } else if (key_info & WPA_KEY_INFO_KEY_TYPE) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key Request for new " + "4-Way Handshake"); + wpa_request_new_ptk(sm); +#ifdef CONFIG_PEERKEY + } else if (msg == SMK_M1) { + wpa_smk_m1(wpa_auth, sm, key, key_data, + key_data_length); +#endif /* CONFIG_PEERKEY */ + } else if (key_data_length > 0 && + wpa_parse_kde_ies(key_data, key_data_length, + &kde) == 0 && + kde.mac_addr) { + } else { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key Request for GTK " + "rekeying"); + eloop_cancel_timeout(wpa_rekey_gtk, wpa_auth, NULL); + wpa_rekey_gtk(wpa_auth, NULL); + } + } else { + /* Do not allow the same key replay counter to be reused. */ + wpa_replay_counter_mark_invalid(sm->key_replay, + key->replay_counter); + + if (msg == PAIRWISE_2) { + /* + * Maintain a copy of the pending EAPOL-Key frames in + * case the EAPOL-Key frame was retransmitted. This is + * needed to allow EAPOL-Key msg 2/4 reply to another + * pending msg 1/4 to update the SNonce to work around + * unexpected supplicant behavior. + */ + os_memcpy(sm->prev_key_replay, sm->key_replay, + sizeof(sm->key_replay)); + } else { + os_memset(sm->prev_key_replay, 0, + sizeof(sm->prev_key_replay)); + } + + /* + * Make sure old valid counters are not accepted anymore and + * do not get copied again. + */ + wpa_replay_counter_mark_invalid(sm->key_replay, NULL); + } + +#ifdef CONFIG_PEERKEY + if (msg == SMK_M3) { + wpa_smk_m3(wpa_auth, sm, key, key_data, key_data_length); + return; + } +#endif /* CONFIG_PEERKEY */ + + os_free(sm->last_rx_eapol_key); + sm->last_rx_eapol_key = os_malloc(data_len); + if (sm->last_rx_eapol_key == NULL) + return; + os_memcpy(sm->last_rx_eapol_key, data, data_len); + sm->last_rx_eapol_key_len = data_len; + + sm->rx_eapol_key_secure = !!(key_info & WPA_KEY_INFO_SECURE); + sm->EAPOLKeyReceived = TRUE; + sm->EAPOLKeyPairwise = !!(key_info & WPA_KEY_INFO_KEY_TYPE); + sm->EAPOLKeyRequest = !!(key_info & WPA_KEY_INFO_REQUEST); + os_memcpy(sm->SNonce, key->key_nonce, WPA_NONCE_LEN); + wpa_sm_step(sm); +} + + +static int wpa_gmk_to_gtk(const u8 *gmk, const char *label, const u8 *addr, + const u8 *gnonce, u8 *gtk, size_t gtk_len) +{ + u8 data[ETH_ALEN + WPA_NONCE_LEN + 8 + 16]; + u8 *pos; + int ret = 0; + + /* GTK = PRF-X(GMK, "Group key expansion", + * AA || GNonce || Time || random data) + * The example described in the IEEE 802.11 standard uses only AA and + * GNonce as inputs here. Add some more entropy since this derivation + * is done only at the Authenticator and as such, does not need to be + * exactly same. + */ + os_memcpy(data, addr, ETH_ALEN); + os_memcpy(data + ETH_ALEN, gnonce, WPA_NONCE_LEN); + pos = data + ETH_ALEN + WPA_NONCE_LEN; + wpa_get_ntp_timestamp(pos); + pos += 8; + if (random_get_bytes(pos, 16) < 0) + ret = -1; + +#ifdef CONFIG_IEEE80211W + sha256_prf(gmk, WPA_GMK_LEN, label, data, sizeof(data), gtk, gtk_len); +#else /* CONFIG_IEEE80211W */ + if (sha1_prf(gmk, WPA_GMK_LEN, label, data, sizeof(data), gtk, gtk_len) + < 0) + ret = -1; +#endif /* CONFIG_IEEE80211W */ + + return ret; +} + + +static void wpa_send_eapol_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + struct wpa_state_machine *sm = timeout_ctx; + + sm->pending_1_of_4_timeout = 0; + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, "EAPOL-Key timeout"); + sm->TimeoutEvt = TRUE; + wpa_sm_step(sm); +} + + +void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int key_info, + const u8 *key_rsc, const u8 *nonce, + const u8 *kde, size_t kde_len, + int keyidx, int encr, int force_version) +{ + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + struct wpa_eapol_key_192 *key192; + size_t len, mic_len, keyhdrlen; + int alg; + int key_data_len, pad_len = 0; + u8 *buf, *pos; + int version, pairwise; + int i; + u8 *key_data; + + mic_len = wpa_mic_len(sm->wpa_key_mgmt); + keyhdrlen = mic_len == 24 ? sizeof(*key192) : sizeof(*key); + + len = sizeof(struct ieee802_1x_hdr) + keyhdrlen; + + if (force_version) + version = force_version; + else if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN || + wpa_key_mgmt_suite_b(sm->wpa_key_mgmt)) + version = WPA_KEY_INFO_TYPE_AKM_DEFINED; + else if (wpa_use_aes_cmac(sm)) + version = WPA_KEY_INFO_TYPE_AES_128_CMAC; + else if (sm->pairwise != WPA_CIPHER_TKIP) + version = WPA_KEY_INFO_TYPE_HMAC_SHA1_AES; + else + version = WPA_KEY_INFO_TYPE_HMAC_MD5_RC4; + + pairwise = !!(key_info & WPA_KEY_INFO_KEY_TYPE); + + wpa_printf(MSG_DEBUG, "WPA: Send EAPOL(version=%d secure=%d mic=%d " + "ack=%d install=%d pairwise=%d kde_len=%lu keyidx=%d " + "encr=%d)", + version, + (key_info & WPA_KEY_INFO_SECURE) ? 1 : 0, + (key_info & WPA_KEY_INFO_MIC) ? 1 : 0, + (key_info & WPA_KEY_INFO_ACK) ? 1 : 0, + (key_info & WPA_KEY_INFO_INSTALL) ? 1 : 0, + pairwise, (unsigned long) kde_len, keyidx, encr); + + key_data_len = kde_len; + + if ((version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN || + wpa_key_mgmt_suite_b(sm->wpa_key_mgmt) || + version == WPA_KEY_INFO_TYPE_AES_128_CMAC) && encr) { + pad_len = key_data_len % 8; + if (pad_len) + pad_len = 8 - pad_len; + key_data_len += pad_len + 8; + } + + len += key_data_len; + + hdr = os_zalloc(len); + if (hdr == NULL) + return; + hdr->version = wpa_auth->conf.eapol_version; + hdr->type = IEEE802_1X_TYPE_EAPOL_KEY; + hdr->length = host_to_be16(len - sizeof(*hdr)); + key = (struct wpa_eapol_key *) (hdr + 1); + key192 = (struct wpa_eapol_key_192 *) (hdr + 1); + key_data = ((u8 *) (hdr + 1)) + keyhdrlen; + + key->type = sm->wpa == WPA_VERSION_WPA2 ? + EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; + key_info |= version; + if (encr && sm->wpa == WPA_VERSION_WPA2) + key_info |= WPA_KEY_INFO_ENCR_KEY_DATA; + if (sm->wpa != WPA_VERSION_WPA2) + key_info |= keyidx << WPA_KEY_INFO_KEY_INDEX_SHIFT; + WPA_PUT_BE16(key->key_info, key_info); + + alg = pairwise ? sm->pairwise : wpa_auth->conf.wpa_group; + WPA_PUT_BE16(key->key_length, wpa_cipher_key_len(alg)); + if (key_info & WPA_KEY_INFO_SMK_MESSAGE) + WPA_PUT_BE16(key->key_length, 0); + + /* FIX: STSL: what to use as key_replay_counter? */ + for (i = RSNA_MAX_EAPOL_RETRIES - 1; i > 0; i--) { + sm->key_replay[i].valid = sm->key_replay[i - 1].valid; + os_memcpy(sm->key_replay[i].counter, + sm->key_replay[i - 1].counter, + WPA_REPLAY_COUNTER_LEN); + } + inc_byte_array(sm->key_replay[0].counter, WPA_REPLAY_COUNTER_LEN); + os_memcpy(key->replay_counter, sm->key_replay[0].counter, + WPA_REPLAY_COUNTER_LEN); + wpa_hexdump(MSG_DEBUG, "WPA: Replay Counter", + key->replay_counter, WPA_REPLAY_COUNTER_LEN); + sm->key_replay[0].valid = TRUE; + + if (nonce) + os_memcpy(key->key_nonce, nonce, WPA_NONCE_LEN); + + if (key_rsc) + os_memcpy(key->key_rsc, key_rsc, WPA_KEY_RSC_LEN); + + if (kde && !encr) { + os_memcpy(key_data, kde, kde_len); + if (mic_len == 24) + WPA_PUT_BE16(key192->key_data_length, kde_len); + else + WPA_PUT_BE16(key->key_data_length, kde_len); + } else if (encr && kde) { + buf = os_zalloc(key_data_len); + if (buf == NULL) { + os_free(hdr); + return; + } + pos = buf; + os_memcpy(pos, kde, kde_len); + pos += kde_len; + + if (pad_len) + *pos++ = 0xdd; + + wpa_hexdump_key(MSG_DEBUG, "Plaintext EAPOL-Key Key Data", + buf, key_data_len); + if (version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN || + wpa_key_mgmt_suite_b(sm->wpa_key_mgmt) || + version == WPA_KEY_INFO_TYPE_AES_128_CMAC) { + if (aes_wrap(sm->PTK.kek, sm->PTK.kek_len, + (key_data_len - 8) / 8, buf, key_data)) { + os_free(hdr); + os_free(buf); + return; + } + if (mic_len == 24) + WPA_PUT_BE16(key192->key_data_length, + key_data_len); + else + WPA_PUT_BE16(key->key_data_length, + key_data_len); +#ifndef CONFIG_NO_RC4 + } else if (sm->PTK.kek_len == 16) { + u8 ek[32]; + os_memcpy(key->key_iv, + sm->group->Counter + WPA_NONCE_LEN - 16, 16); + inc_byte_array(sm->group->Counter, WPA_NONCE_LEN); + os_memcpy(ek, key->key_iv, 16); + os_memcpy(ek + 16, sm->PTK.kek, sm->PTK.kek_len); + os_memcpy(key_data, buf, key_data_len); + rc4_skip(ek, 32, 256, key_data, key_data_len); + if (mic_len == 24) + WPA_PUT_BE16(key192->key_data_length, + key_data_len); + else + WPA_PUT_BE16(key->key_data_length, + key_data_len); +#endif /* CONFIG_NO_RC4 */ + } else { + os_free(hdr); + os_free(buf); + return; + } + os_free(buf); + } + + if (key_info & WPA_KEY_INFO_MIC) { + u8 *key_mic; + + if (!sm->PTK_valid) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, + "PTK not valid when sending EAPOL-Key " + "frame"); + os_free(hdr); + return; + } + + key_mic = key192->key_mic; /* same offset for key and key192 */ + wpa_eapol_key_mic(sm->PTK.kck, sm->PTK.kck_len, + sm->wpa_key_mgmt, version, + (u8 *) hdr, len, key_mic); +#ifdef CONFIG_TESTING_OPTIONS + if (!pairwise && + wpa_auth->conf.corrupt_gtk_rekey_mic_probability > 0.0 && + drand48() < + wpa_auth->conf.corrupt_gtk_rekey_mic_probability) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "Corrupting group EAPOL-Key Key MIC"); + key_mic[0]++; + } +#endif /* CONFIG_TESTING_OPTIONS */ + } + + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_inc_EapolFramesTx, + 1); + wpa_auth_send_eapol(wpa_auth, sm->addr, (u8 *) hdr, len, + sm->pairwise_set); + os_free(hdr); +} + + +static void wpa_send_eapol(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int key_info, + const u8 *key_rsc, const u8 *nonce, + const u8 *kde, size_t kde_len, + int keyidx, int encr) +{ + int timeout_ms; + int pairwise = key_info & WPA_KEY_INFO_KEY_TYPE; + int ctr; + + if (sm == NULL) + return; + + __wpa_send_eapol(wpa_auth, sm, key_info, key_rsc, nonce, kde, kde_len, + keyidx, encr, 0); + + ctr = pairwise ? sm->TimeoutCtr : sm->GTimeoutCtr; + if (ctr == 1 && wpa_auth->conf.tx_status) + timeout_ms = pairwise ? eapol_key_timeout_first : + eapol_key_timeout_first_group; + else + timeout_ms = eapol_key_timeout_subseq; + if (pairwise && ctr == 1 && !(key_info & WPA_KEY_INFO_MIC)) + sm->pending_1_of_4_timeout = 1; + wpa_printf(MSG_DEBUG, "WPA: Use EAPOL-Key timeout of %u ms (retry " + "counter %d)", timeout_ms, ctr); + eloop_register_timeout(timeout_ms / 1000, (timeout_ms % 1000) * 1000, + wpa_send_eapol_timeout, wpa_auth, sm); +} + + +static int wpa_verify_key_mic(int akmp, struct wpa_ptk *PTK, u8 *data, + size_t data_len) +{ + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + struct wpa_eapol_key_192 *key192; + u16 key_info; + int ret = 0; + u8 mic[WPA_EAPOL_KEY_MIC_MAX_LEN]; + size_t mic_len = wpa_mic_len(akmp); + + if (data_len < sizeof(*hdr) + sizeof(*key)) + return -1; + + hdr = (struct ieee802_1x_hdr *) data; + key = (struct wpa_eapol_key *) (hdr + 1); + key192 = (struct wpa_eapol_key_192 *) (hdr + 1); + key_info = WPA_GET_BE16(key->key_info); + os_memcpy(mic, key192->key_mic, mic_len); + os_memset(key192->key_mic, 0, mic_len); + if (wpa_eapol_key_mic(PTK->kck, PTK->kck_len, akmp, + key_info & WPA_KEY_INFO_TYPE_MASK, + data, data_len, key192->key_mic) || + os_memcmp_const(mic, key192->key_mic, mic_len) != 0) + ret = -1; + os_memcpy(key192->key_mic, mic, mic_len); + return ret; +} + + +void wpa_remove_ptk(struct wpa_state_machine *sm) +{ + sm->PTK_valid = FALSE; + os_memset(&sm->PTK, 0, sizeof(sm->PTK)); + wpa_auth_set_key(sm->wpa_auth, 0, WPA_ALG_NONE, sm->addr, 0, NULL, 0); + sm->pairwise_set = FALSE; + eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm); +} + + +int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event) +{ + int remove_ptk = 1; + + if (sm == NULL) + return -1; + + wpa_auth_vlogger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "event %d notification", event); + + switch (event) { + case WPA_AUTH: +#ifdef CONFIG_MESH + /* PTKs are derived through AMPE */ + if (wpa_auth_start_ampe(sm->wpa_auth, sm->addr)) { + /* not mesh */ + break; + } + return 0; +#endif /* CONFIG_MESH */ + case WPA_ASSOC: + break; + case WPA_DEAUTH: + case WPA_DISASSOC: + sm->DeauthenticationRequest = TRUE; + break; + case WPA_REAUTH: + case WPA_REAUTH_EAPOL: + if (!sm->started) { + /* + * When using WPS, we may end up here if the STA + * manages to re-associate without the previous STA + * entry getting removed. Consequently, we need to make + * sure that the WPA state machines gets initialized + * properly at this point. + */ + wpa_printf(MSG_DEBUG, "WPA state machine had not been " + "started - initialize now"); + sm->started = 1; + sm->Init = TRUE; + if (wpa_sm_step(sm) == 1) + return 1; /* should not really happen */ + sm->Init = FALSE; + sm->AuthenticationRequest = TRUE; + break; + } + if (sm->GUpdateStationKeys) { + /* + * Reauthentication cancels the pending group key + * update for this STA. + */ + sm->group->GKeyDoneStations--; + sm->GUpdateStationKeys = FALSE; + sm->PtkGroupInit = TRUE; + } + sm->ReAuthenticationRequest = TRUE; + break; + case WPA_ASSOC_FT: +#ifdef CONFIG_IEEE80211R + wpa_printf(MSG_DEBUG, "FT: Retry PTK configuration " + "after association"); + wpa_ft_install_ptk(sm); + + /* Using FT protocol, not WPA auth state machine */ + sm->ft_completed = 1; + return 0; +#else /* CONFIG_IEEE80211R */ + break; +#endif /* CONFIG_IEEE80211R */ + } + +#ifdef CONFIG_IEEE80211R + sm->ft_completed = 0; +#endif /* CONFIG_IEEE80211R */ + +#ifdef CONFIG_IEEE80211W + if (sm->mgmt_frame_prot && event == WPA_AUTH) + remove_ptk = 0; +#endif /* CONFIG_IEEE80211W */ + + if (remove_ptk) { + sm->PTK_valid = FALSE; + os_memset(&sm->PTK, 0, sizeof(sm->PTK)); + + if (event != WPA_REAUTH_EAPOL) + wpa_remove_ptk(sm); + } + + if (sm->in_step_loop) { + /* + * wpa_sm_step() is already running - avoid recursive call to + * it by making the existing loop process the new update. + */ + sm->changed = TRUE; + return 0; + } + return wpa_sm_step(sm); +} + + +SM_STATE(WPA_PTK, INITIALIZE) +{ + SM_ENTRY_MA(WPA_PTK, INITIALIZE, wpa_ptk); + if (sm->Init) { + /* Init flag is not cleared here, so avoid busy + * loop by claiming nothing changed. */ + sm->changed = FALSE; + } + + sm->keycount = 0; + if (sm->GUpdateStationKeys) + sm->group->GKeyDoneStations--; + sm->GUpdateStationKeys = FALSE; + if (sm->wpa == WPA_VERSION_WPA) + sm->PInitAKeys = FALSE; + if (1 /* Unicast cipher supported AND (ESS OR ((IBSS or WDS) and + * Local AA > Remote AA)) */) { + sm->Pair = TRUE; + } + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portEnabled, 0); + wpa_remove_ptk(sm); + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portValid, 0); + sm->TimeoutCtr = 0; + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_authorized, 0); + } +} + + +SM_STATE(WPA_PTK, DISCONNECT) +{ + SM_ENTRY_MA(WPA_PTK, DISCONNECT, wpa_ptk); + sm->Disconnect = FALSE; + wpa_sta_disconnect(sm->wpa_auth, sm->addr); +} + + +SM_STATE(WPA_PTK, DISCONNECTED) +{ + SM_ENTRY_MA(WPA_PTK, DISCONNECTED, wpa_ptk); + sm->DeauthenticationRequest = FALSE; +} + + +SM_STATE(WPA_PTK, AUTHENTICATION) +{ + SM_ENTRY_MA(WPA_PTK, AUTHENTICATION, wpa_ptk); + os_memset(&sm->PTK, 0, sizeof(sm->PTK)); + sm->PTK_valid = FALSE; + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portControl_Auto, + 1); + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portEnabled, 1); + sm->AuthenticationRequest = FALSE; +} + + +static void wpa_group_ensure_init(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + if (group->first_sta_seen) + return; + /* + * System has run bit further than at the time hostapd was started + * potentially very early during boot up. This provides better chances + * of collecting more randomness on embedded systems. Re-initialize the + * GMK and Counter here to improve their strength if there was not + * enough entropy available immediately after system startup. + */ + wpa_printf(MSG_DEBUG, "WPA: Re-initialize GMK/Counter on first " + "station"); + if (random_pool_ready() != 1) { + wpa_printf(MSG_INFO, "WPA: Not enough entropy in random pool " + "to proceed - reject first 4-way handshake"); + group->reject_4way_hs_for_entropy = TRUE; + } else { + group->first_sta_seen = TRUE; + group->reject_4way_hs_for_entropy = FALSE; + } + + if (wpa_group_init_gmk_and_counter(wpa_auth, group) < 0 || + wpa_gtk_update(wpa_auth, group) < 0 || + wpa_group_config_group_keys(wpa_auth, group) < 0) { + wpa_printf(MSG_INFO, "WPA: GMK/GTK setup failed"); + group->first_sta_seen = FALSE; + group->reject_4way_hs_for_entropy = TRUE; + } +} + + +SM_STATE(WPA_PTK, AUTHENTICATION2) +{ + SM_ENTRY_MA(WPA_PTK, AUTHENTICATION2, wpa_ptk); + + wpa_group_ensure_init(sm->wpa_auth, sm->group); + sm->ReAuthenticationRequest = FALSE; + + /* + * Definition of ANonce selection in IEEE Std 802.11i-2004 is somewhat + * ambiguous. The Authenticator state machine uses a counter that is + * incremented by one for each 4-way handshake. However, the security + * analysis of 4-way handshake points out that unpredictable nonces + * help in preventing precomputation attacks. Instead of the state + * machine definition, use an unpredictable nonce value here to provide + * stronger protection against potential precomputation attacks. + */ + if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) { + wpa_printf(MSG_ERROR, "WPA: Failed to get random data for " + "ANonce."); + sm->Disconnect = TRUE; + return; + } + wpa_hexdump(MSG_DEBUG, "WPA: Assign ANonce", sm->ANonce, + WPA_NONCE_LEN); + /* IEEE 802.11i does not clear TimeoutCtr here, but this is more + * logical place than INITIALIZE since AUTHENTICATION2 can be + * re-entered on ReAuthenticationRequest without going through + * INITIALIZE. */ + sm->TimeoutCtr = 0; +} + + +SM_STATE(WPA_PTK, INITPMK) +{ + u8 msk[2 * PMK_LEN]; + size_t len = 2 * PMK_LEN; + + SM_ENTRY_MA(WPA_PTK, INITPMK, wpa_ptk); +#ifdef CONFIG_IEEE80211R + sm->xxkey_len = 0; +#endif /* CONFIG_IEEE80211R */ + if (sm->pmksa) { + wpa_printf(MSG_DEBUG, "WPA: PMK from PMKSA cache"); + os_memcpy(sm->PMK, sm->pmksa->pmk, PMK_LEN); + } else if (wpa_auth_get_msk(sm->wpa_auth, sm->addr, msk, &len) == 0) { + wpa_printf(MSG_DEBUG, "WPA: PMK from EAPOL state machine " + "(len=%lu)", (unsigned long) len); + os_memcpy(sm->PMK, msk, PMK_LEN); +#ifdef CONFIG_IEEE80211R + if (len >= 2 * PMK_LEN) { + os_memcpy(sm->xxkey, msk + PMK_LEN, PMK_LEN); + sm->xxkey_len = PMK_LEN; + } +#endif /* CONFIG_IEEE80211R */ + } else { + wpa_printf(MSG_DEBUG, "WPA: Could not get PMK, get_msk: %p", + sm->wpa_auth->cb.get_msk); + sm->Disconnect = TRUE; + return; + } + os_memset(msk, 0, sizeof(msk)); + + sm->req_replay_counter_used = 0; + /* IEEE 802.11i does not set keyRun to FALSE, but not doing this + * will break reauthentication since EAPOL state machines may not be + * get into AUTHENTICATING state that clears keyRun before WPA state + * machine enters AUTHENTICATION2 state and goes immediately to INITPMK + * state and takes PMK from the previously used AAA Key. This will + * eventually fail in 4-Way Handshake because Supplicant uses PMK + * derived from the new AAA Key. Setting keyRun = FALSE here seems to + * be good workaround for this issue. */ + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_keyRun, 0); +} + + +SM_STATE(WPA_PTK, INITPSK) +{ + const u8 *psk; + SM_ENTRY_MA(WPA_PTK, INITPSK, wpa_ptk); + psk = wpa_auth_get_psk(sm->wpa_auth, sm->addr, sm->p2p_dev_addr, NULL); + if (psk) { + os_memcpy(sm->PMK, psk, PMK_LEN); +#ifdef CONFIG_IEEE80211R + os_memcpy(sm->xxkey, psk, PMK_LEN); + sm->xxkey_len = PMK_LEN; +#endif /* CONFIG_IEEE80211R */ + } + sm->req_replay_counter_used = 0; +} + + +SM_STATE(WPA_PTK, PTKSTART) +{ + u8 buf[2 + RSN_SELECTOR_LEN + PMKID_LEN], *pmkid = NULL; + size_t pmkid_len = 0; + + SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk); + sm->PTKRequest = FALSE; + sm->TimeoutEvt = FALSE; + sm->alt_snonce_valid = FALSE; + + sm->TimeoutCtr++; + if (sm->TimeoutCtr > (int) dot11RSNAConfigPairwiseUpdateCount) { + /* No point in sending the EAPOL-Key - we will disconnect + * immediately following this. */ + return; + } + + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "sending 1/4 msg of 4-Way Handshake"); + /* + * TODO: Could add PMKID even with WPA2-PSK, but only if there is only + * one possible PSK for this STA. + */ + if (sm->wpa == WPA_VERSION_WPA2 && + wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt) && + sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN) { + pmkid = buf; + pmkid_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN; + pmkid[0] = WLAN_EID_VENDOR_SPECIFIC; + pmkid[1] = RSN_SELECTOR_LEN + PMKID_LEN; + RSN_SELECTOR_PUT(&pmkid[2], RSN_KEY_DATA_PMKID); + if (sm->pmksa) { + os_memcpy(&pmkid[2 + RSN_SELECTOR_LEN], + sm->pmksa->pmkid, PMKID_LEN); + } else if (wpa_key_mgmt_suite_b(sm->wpa_key_mgmt)) { + /* No KCK available to derive PMKID */ + pmkid = NULL; + } else { + /* + * Calculate PMKID since no PMKSA cache entry was + * available with pre-calculated PMKID. + */ + rsn_pmkid(sm->PMK, PMK_LEN, sm->wpa_auth->addr, + sm->addr, &pmkid[2 + RSN_SELECTOR_LEN], + wpa_key_mgmt_sha256(sm->wpa_key_mgmt)); + } + } + wpa_send_eapol(sm->wpa_auth, sm, + WPA_KEY_INFO_ACK | WPA_KEY_INFO_KEY_TYPE, NULL, + sm->ANonce, pmkid, pmkid_len, 0, 0); +} + + +static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce, + const u8 *pmk, struct wpa_ptk *ptk) +{ +#ifdef CONFIG_IEEE80211R + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) + return wpa_auth_derive_ptk_ft(sm, pmk, ptk); +#endif /* CONFIG_IEEE80211R */ + + return wpa_pmk_to_ptk(pmk, PMK_LEN, "Pairwise key expansion", + sm->wpa_auth->addr, sm->addr, sm->ANonce, snonce, + ptk, sm->wpa_key_mgmt, sm->pairwise); +} + + +SM_STATE(WPA_PTK, PTKCALCNEGOTIATING) +{ + struct wpa_ptk PTK; + int ok = 0, psk_found = 0; + const u8 *pmk = NULL; + + SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING, wpa_ptk); + sm->EAPOLKeyReceived = FALSE; + sm->update_snonce = FALSE; + + /* WPA with IEEE 802.1X: use the derived PMK from EAP + * WPA-PSK: iterate through possible PSKs and select the one matching + * the packet */ + for (;;) { + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) { + pmk = wpa_auth_get_psk(sm->wpa_auth, sm->addr, + sm->p2p_dev_addr, pmk); + if (pmk == NULL) + break; + psk_found = 1; + } else + pmk = sm->PMK; + + wpa_derive_ptk(sm, sm->SNonce, pmk, &PTK); + + if (wpa_verify_key_mic(sm->wpa_key_mgmt, &PTK, + sm->last_rx_eapol_key, + sm->last_rx_eapol_key_len) == 0) { + ok = 1; + break; + } + + if (!wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) + break; + } + + if (!ok) { + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "invalid MIC in msg 2/4 of 4-Way Handshake"); + if (psk_found) + wpa_auth_psk_failure_report(sm->wpa_auth, sm->addr); + return; + } + +#ifdef CONFIG_IEEE80211R + if (sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + /* + * Verify that PMKR1Name from EAPOL-Key message 2/4 matches + * with the value we derived. + */ + if (os_memcmp_const(sm->sup_pmk_r1_name, sm->pmk_r1_name, + WPA_PMK_NAME_LEN) != 0) { + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "PMKR1Name mismatch in FT 4-way " + "handshake"); + wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name from " + "Supplicant", + sm->sup_pmk_r1_name, WPA_PMK_NAME_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name", + sm->pmk_r1_name, WPA_PMK_NAME_LEN); + return; + } + } +#endif /* CONFIG_IEEE80211R */ + + sm->pending_1_of_4_timeout = 0; + eloop_cancel_timeout(wpa_send_eapol_timeout, sm->wpa_auth, sm); + + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) { + /* PSK may have changed from the previous choice, so update + * state machine data based on whatever PSK was selected here. + */ + os_memcpy(sm->PMK, pmk, PMK_LEN); + } + + sm->MICVerified = TRUE; + + os_memcpy(&sm->PTK, &PTK, sizeof(PTK)); + sm->PTK_valid = TRUE; +} + + +SM_STATE(WPA_PTK, PTKCALCNEGOTIATING2) +{ + SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING2, wpa_ptk); + sm->TimeoutCtr = 0; +} + + +#ifdef CONFIG_IEEE80211W + +static int ieee80211w_kde_len(struct wpa_state_machine *sm) +{ + if (sm->mgmt_frame_prot) { + size_t len; + len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher); + return 2 + RSN_SELECTOR_LEN + WPA_IGTK_KDE_PREFIX_LEN + len; + } + + return 0; +} + + +static u8 * ieee80211w_kde_add(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_igtk_kde igtk; + struct wpa_group *gsm = sm->group; + u8 rsc[WPA_KEY_RSC_LEN]; + size_t len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher); + + if (!sm->mgmt_frame_prot) + return pos; + + igtk.keyid[0] = gsm->GN_igtk; + igtk.keyid[1] = 0; + if (gsm->wpa_group_state != WPA_GROUP_SETKEYSDONE || + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, rsc) < 0) + os_memset(igtk.pn, 0, sizeof(igtk.pn)); + else + os_memcpy(igtk.pn, rsc, sizeof(igtk.pn)); + os_memcpy(igtk.igtk, gsm->IGTK[gsm->GN_igtk - 4], len); + if (sm->wpa_auth->conf.disable_gtk) { + /* + * Provide unique random IGTK to each STA to prevent use of + * IGTK in the BSS. + */ + if (random_get_bytes(igtk.igtk, len) < 0) + return pos; + } + pos = wpa_add_kde(pos, RSN_KEY_DATA_IGTK, + (const u8 *) &igtk, WPA_IGTK_KDE_PREFIX_LEN + len, + NULL, 0); + + return pos; +} + +#else /* CONFIG_IEEE80211W */ + +static int ieee80211w_kde_len(struct wpa_state_machine *sm) +{ + return 0; +} + + +static u8 * ieee80211w_kde_add(struct wpa_state_machine *sm, u8 *pos) +{ + return pos; +} + +#endif /* CONFIG_IEEE80211W */ + + +SM_STATE(WPA_PTK, PTKINITNEGOTIATING) +{ + u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde, *pos, dummy_gtk[32]; + size_t gtk_len, kde_len; + struct wpa_group *gsm = sm->group; + u8 *wpa_ie; + int wpa_ie_len, secure, keyidx, encr = 0; + + SM_ENTRY_MA(WPA_PTK, PTKINITNEGOTIATING, wpa_ptk); + sm->TimeoutEvt = FALSE; + + sm->TimeoutCtr++; + if (sm->TimeoutCtr > (int) dot11RSNAConfigPairwiseUpdateCount) { + /* No point in sending the EAPOL-Key - we will disconnect + * immediately following this. */ + return; + } + + /* Send EAPOL(1, 1, 1, Pair, P, RSC, ANonce, MIC(PTK), RSNIE, [MDIE], + GTK[GN], IGTK, [FTIE], [TIE * 2]) + */ + os_memset(rsc, 0, WPA_KEY_RSC_LEN); + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, rsc); + /* If FT is used, wpa_auth->wpa_ie includes both RSNIE and MDIE */ + wpa_ie = sm->wpa_auth->wpa_ie; + wpa_ie_len = sm->wpa_auth->wpa_ie_len; + if (sm->wpa == WPA_VERSION_WPA && + (sm->wpa_auth->conf.wpa & WPA_PROTO_RSN) && + wpa_ie_len > wpa_ie[1] + 2 && wpa_ie[0] == WLAN_EID_RSN) { + /* WPA-only STA, remove RSN IE and possible MDIE */ + wpa_ie = wpa_ie + wpa_ie[1] + 2; + if (wpa_ie[0] == WLAN_EID_MOBILITY_DOMAIN) + wpa_ie = wpa_ie + wpa_ie[1] + 2; + wpa_ie_len = wpa_ie[1] + 2; + } + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "sending 3/4 msg of 4-Way Handshake"); + if (sm->wpa == WPA_VERSION_WPA2) { + /* WPA2 send GTK in the 4-way handshake */ + secure = 1; + gtk = gsm->GTK[gsm->GN - 1]; + gtk_len = gsm->GTK_len; + if (sm->wpa_auth->conf.disable_gtk) { + /* + * Provide unique random GTK to each STA to prevent use + * of GTK in the BSS. + */ + if (random_get_bytes(dummy_gtk, gtk_len) < 0) + return; + gtk = dummy_gtk; + } + keyidx = gsm->GN; + _rsc = rsc; + encr = 1; + } else { + /* WPA does not include GTK in msg 3/4 */ + secure = 0; + gtk = NULL; + gtk_len = 0; + keyidx = 0; + _rsc = NULL; + if (sm->rx_eapol_key_secure) { + /* + * It looks like Windows 7 supplicant tries to use + * Secure bit in msg 2/4 after having reported Michael + * MIC failure and it then rejects the 4-way handshake + * if msg 3/4 does not set Secure bit. Work around this + * by setting the Secure bit here even in the case of + * WPA if the supplicant used it first. + */ + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "STA used Secure bit in WPA msg 2/4 - " + "set Secure for 3/4 as workaround"); + secure = 1; + } + } + + kde_len = wpa_ie_len + ieee80211w_kde_len(sm); + if (gtk) + kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len; +#ifdef CONFIG_IEEE80211R + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + kde_len += 2 + PMKID_LEN; /* PMKR1Name into RSN IE */ + kde_len += 300; /* FTIE + 2 * TIE */ + } +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_P2P + if (WPA_GET_BE32(sm->ip_addr) > 0) + kde_len += 2 + RSN_SELECTOR_LEN + 3 * 4; +#endif /* CONFIG_P2P */ + kde = os_malloc(kde_len); + if (kde == NULL) + return; + + pos = kde; + os_memcpy(pos, wpa_ie, wpa_ie_len); + pos += wpa_ie_len; +#ifdef CONFIG_IEEE80211R + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + int res = wpa_insert_pmkid(kde, pos - kde, sm->pmk_r1_name); + if (res < 0) { + wpa_printf(MSG_ERROR, "FT: Failed to insert " + "PMKR1Name into RSN IE in EAPOL-Key data"); + os_free(kde); + return; + } + pos += res; + } +#endif /* CONFIG_IEEE80211R */ + if (gtk) { + u8 hdr[2]; + hdr[0] = keyidx & 0x03; + hdr[1] = 0; + pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, + gtk, gtk_len); + } + pos = ieee80211w_kde_add(sm, pos); + +#ifdef CONFIG_IEEE80211R + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + int res; + struct wpa_auth_config *conf; + + conf = &sm->wpa_auth->conf; + res = wpa_write_ftie(conf, conf->r0_key_holder, + conf->r0_key_holder_len, + NULL, NULL, pos, kde + kde_len - pos, + NULL, 0); + if (res < 0) { + wpa_printf(MSG_ERROR, "FT: Failed to insert FTIE " + "into EAPOL-Key Key Data"); + os_free(kde); + return; + } + pos += res; + + /* TIE[ReassociationDeadline] (TU) */ + *pos++ = WLAN_EID_TIMEOUT_INTERVAL; + *pos++ = 5; + *pos++ = WLAN_TIMEOUT_REASSOC_DEADLINE; + WPA_PUT_LE32(pos, conf->reassociation_deadline); + pos += 4; + + /* TIE[KeyLifetime] (seconds) */ + *pos++ = WLAN_EID_TIMEOUT_INTERVAL; + *pos++ = 5; + *pos++ = WLAN_TIMEOUT_KEY_LIFETIME; + WPA_PUT_LE32(pos, conf->r0_key_lifetime * 60); + pos += 4; + } +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_P2P + if (WPA_GET_BE32(sm->ip_addr) > 0) { + u8 addr[3 * 4]; + os_memcpy(addr, sm->ip_addr, 4); + os_memcpy(addr + 4, sm->wpa_auth->conf.ip_addr_mask, 4); + os_memcpy(addr + 8, sm->wpa_auth->conf.ip_addr_go, 4); + pos = wpa_add_kde(pos, WFA_KEY_DATA_IP_ADDR_ALLOC, + addr, sizeof(addr), NULL, 0); + } +#endif /* CONFIG_P2P */ + + wpa_send_eapol(sm->wpa_auth, sm, + (secure ? WPA_KEY_INFO_SECURE : 0) | WPA_KEY_INFO_MIC | + WPA_KEY_INFO_ACK | WPA_KEY_INFO_INSTALL | + WPA_KEY_INFO_KEY_TYPE, + _rsc, sm->ANonce, kde, pos - kde, keyidx, encr); + os_free(kde); +} + + +SM_STATE(WPA_PTK, PTKINITDONE) +{ + SM_ENTRY_MA(WPA_PTK, PTKINITDONE, wpa_ptk); + sm->EAPOLKeyReceived = FALSE; + if (sm->Pair) { + enum wpa_alg alg = wpa_cipher_to_alg(sm->pairwise); + int klen = wpa_cipher_key_len(sm->pairwise); + if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0, + sm->PTK.tk, klen)) { + wpa_sta_disconnect(sm->wpa_auth, sm->addr); + return; + } + /* FIX: MLME-SetProtection.Request(TA, Tx_Rx) */ + sm->pairwise_set = TRUE; + + if (sm->wpa_auth->conf.wpa_ptk_rekey) { + eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm); + eloop_register_timeout(sm->wpa_auth->conf. + wpa_ptk_rekey, 0, wpa_rekey_ptk, + sm->wpa_auth, sm); + } + + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_authorized, 1); + } + } + + if (0 /* IBSS == TRUE */) { + sm->keycount++; + if (sm->keycount == 2) { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_portValid, 1); + } + } else { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portValid, + 1); + } + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_keyAvailable, 0); + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_keyDone, 1); + if (sm->wpa == WPA_VERSION_WPA) + sm->PInitAKeys = TRUE; + else + sm->has_GTK = TRUE; + wpa_auth_vlogger(sm->wpa_auth, sm->addr, LOGGER_INFO, + "pairwise key handshake completed (%s)", + sm->wpa == WPA_VERSION_WPA ? "WPA" : "RSN"); + +#ifdef CONFIG_IEEE80211R + wpa_ft_push_pmk_r1(sm->wpa_auth, sm->addr); +#endif /* CONFIG_IEEE80211R */ +} + + +SM_STEP(WPA_PTK) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + + if (sm->Init) + SM_ENTER(WPA_PTK, INITIALIZE); + else if (sm->Disconnect + /* || FIX: dot11RSNAConfigSALifetime timeout */) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, + "WPA_PTK: sm->Disconnect"); + SM_ENTER(WPA_PTK, DISCONNECT); + } + else if (sm->DeauthenticationRequest) + SM_ENTER(WPA_PTK, DISCONNECTED); + else if (sm->AuthenticationRequest) + SM_ENTER(WPA_PTK, AUTHENTICATION); + else if (sm->ReAuthenticationRequest) + SM_ENTER(WPA_PTK, AUTHENTICATION2); + else if (sm->PTKRequest) + SM_ENTER(WPA_PTK, PTKSTART); + else switch (sm->wpa_ptk_state) { + case WPA_PTK_INITIALIZE: + break; + case WPA_PTK_DISCONNECT: + SM_ENTER(WPA_PTK, DISCONNECTED); + break; + case WPA_PTK_DISCONNECTED: + SM_ENTER(WPA_PTK, INITIALIZE); + break; + case WPA_PTK_AUTHENTICATION: + SM_ENTER(WPA_PTK, AUTHENTICATION2); + break; + case WPA_PTK_AUTHENTICATION2: + if (wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt) && + wpa_auth_get_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_keyRun) > 0) + SM_ENTER(WPA_PTK, INITPMK); + else if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) + /* FIX: && 802.1X::keyRun */) + SM_ENTER(WPA_PTK, INITPSK); + break; + case WPA_PTK_INITPMK: + if (wpa_auth_get_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_keyAvailable) > 0) + SM_ENTER(WPA_PTK, PTKSTART); + else { + wpa_auth->dot11RSNA4WayHandshakeFailures++; + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_INFO, + "INITPMK - keyAvailable = false"); + SM_ENTER(WPA_PTK, DISCONNECT); + } + break; + case WPA_PTK_INITPSK: + if (wpa_auth_get_psk(sm->wpa_auth, sm->addr, sm->p2p_dev_addr, + NULL)) + SM_ENTER(WPA_PTK, PTKSTART); + else { + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_INFO, + "no PSK configured for the STA"); + wpa_auth->dot11RSNA4WayHandshakeFailures++; + SM_ENTER(WPA_PTK, DISCONNECT); + } + break; + case WPA_PTK_PTKSTART: + if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + sm->EAPOLKeyPairwise) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING); + else if (sm->TimeoutCtr > + (int) dot11RSNAConfigPairwiseUpdateCount) { + wpa_auth->dot11RSNA4WayHandshakeFailures++; + wpa_auth_vlogger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "PTKSTART: Retry limit %d reached", + dot11RSNAConfigPairwiseUpdateCount); + SM_ENTER(WPA_PTK, DISCONNECT); + } else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK, PTKSTART); + break; + case WPA_PTK_PTKCALCNEGOTIATING: + if (sm->MICVerified) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING2); + else if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + sm->EAPOLKeyPairwise) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING); + else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK, PTKSTART); + break; + case WPA_PTK_PTKCALCNEGOTIATING2: + SM_ENTER(WPA_PTK, PTKINITNEGOTIATING); + break; + case WPA_PTK_PTKINITNEGOTIATING: + if (sm->update_snonce) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING); + else if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + sm->EAPOLKeyPairwise && sm->MICVerified) + SM_ENTER(WPA_PTK, PTKINITDONE); + else if (sm->TimeoutCtr > + (int) dot11RSNAConfigPairwiseUpdateCount) { + wpa_auth->dot11RSNA4WayHandshakeFailures++; + wpa_auth_vlogger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "PTKINITNEGOTIATING: Retry limit %d " + "reached", + dot11RSNAConfigPairwiseUpdateCount); + SM_ENTER(WPA_PTK, DISCONNECT); + } else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK, PTKINITNEGOTIATING); + break; + case WPA_PTK_PTKINITDONE: + break; + } +} + + +SM_STATE(WPA_PTK_GROUP, IDLE) +{ + SM_ENTRY_MA(WPA_PTK_GROUP, IDLE, wpa_ptk_group); + if (sm->Init) { + /* Init flag is not cleared here, so avoid busy + * loop by claiming nothing changed. */ + sm->changed = FALSE; + } + sm->GTimeoutCtr = 0; +} + + +SM_STATE(WPA_PTK_GROUP, REKEYNEGOTIATING) +{ + u8 rsc[WPA_KEY_RSC_LEN]; + struct wpa_group *gsm = sm->group; + const u8 *kde; + u8 *kde_buf = NULL, *pos, hdr[2]; + size_t kde_len; + u8 *gtk, dummy_gtk[32]; + + SM_ENTRY_MA(WPA_PTK_GROUP, REKEYNEGOTIATING, wpa_ptk_group); + + sm->GTimeoutCtr++; + if (sm->GTimeoutCtr > (int) dot11RSNAConfigGroupUpdateCount) { + /* No point in sending the EAPOL-Key - we will disconnect + * immediately following this. */ + return; + } + + if (sm->wpa == WPA_VERSION_WPA) + sm->PInitAKeys = FALSE; + sm->TimeoutEvt = FALSE; + /* Send EAPOL(1, 1, 1, !Pair, G, RSC, GNonce, MIC(PTK), GTK[GN]) */ + os_memset(rsc, 0, WPA_KEY_RSC_LEN); + if (gsm->wpa_group_state == WPA_GROUP_SETKEYSDONE) + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, rsc); + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "sending 1/2 msg of Group Key Handshake"); + + gtk = gsm->GTK[gsm->GN - 1]; + if (sm->wpa_auth->conf.disable_gtk) { + /* + * Provide unique random GTK to each STA to prevent use + * of GTK in the BSS. + */ + if (random_get_bytes(dummy_gtk, gsm->GTK_len) < 0) + return; + gtk = dummy_gtk; + } + if (sm->wpa == WPA_VERSION_WPA2) { + kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len + + ieee80211w_kde_len(sm); + kde_buf = os_malloc(kde_len); + if (kde_buf == NULL) + return; + + kde = pos = kde_buf; + hdr[0] = gsm->GN & 0x03; + hdr[1] = 0; + pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, + gtk, gsm->GTK_len); + pos = ieee80211w_kde_add(sm, pos); + kde_len = pos - kde; + } else { + kde = gtk; + kde_len = gsm->GTK_len; + } + + wpa_send_eapol(sm->wpa_auth, sm, + WPA_KEY_INFO_SECURE | WPA_KEY_INFO_MIC | + WPA_KEY_INFO_ACK | + (!sm->Pair ? WPA_KEY_INFO_INSTALL : 0), + rsc, gsm->GNonce, kde, kde_len, gsm->GN, 1); + + os_free(kde_buf); +} + + +SM_STATE(WPA_PTK_GROUP, REKEYESTABLISHED) +{ + SM_ENTRY_MA(WPA_PTK_GROUP, REKEYESTABLISHED, wpa_ptk_group); + sm->EAPOLKeyReceived = FALSE; + if (sm->GUpdateStationKeys) + sm->group->GKeyDoneStations--; + sm->GUpdateStationKeys = FALSE; + sm->GTimeoutCtr = 0; + /* FIX: MLME.SetProtection.Request(TA, Tx_Rx) */ + wpa_auth_vlogger(sm->wpa_auth, sm->addr, LOGGER_INFO, + "group key handshake completed (%s)", + sm->wpa == WPA_VERSION_WPA ? "WPA" : "RSN"); + sm->has_GTK = TRUE; +} + + +SM_STATE(WPA_PTK_GROUP, KEYERROR) +{ + SM_ENTRY_MA(WPA_PTK_GROUP, KEYERROR, wpa_ptk_group); + if (sm->GUpdateStationKeys) + sm->group->GKeyDoneStations--; + sm->GUpdateStationKeys = FALSE; + sm->Disconnect = TRUE; +} + + +SM_STEP(WPA_PTK_GROUP) +{ + if (sm->Init || sm->PtkGroupInit) { + SM_ENTER(WPA_PTK_GROUP, IDLE); + sm->PtkGroupInit = FALSE; + } else switch (sm->wpa_ptk_group_state) { + case WPA_PTK_GROUP_IDLE: + if (sm->GUpdateStationKeys || + (sm->wpa == WPA_VERSION_WPA && sm->PInitAKeys)) + SM_ENTER(WPA_PTK_GROUP, REKEYNEGOTIATING); + break; + case WPA_PTK_GROUP_REKEYNEGOTIATING: + if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + !sm->EAPOLKeyPairwise && sm->MICVerified) + SM_ENTER(WPA_PTK_GROUP, REKEYESTABLISHED); + else if (sm->GTimeoutCtr > + (int) dot11RSNAConfigGroupUpdateCount) + SM_ENTER(WPA_PTK_GROUP, KEYERROR); + else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK_GROUP, REKEYNEGOTIATING); + break; + case WPA_PTK_GROUP_KEYERROR: + SM_ENTER(WPA_PTK_GROUP, IDLE); + break; + case WPA_PTK_GROUP_REKEYESTABLISHED: + SM_ENTER(WPA_PTK_GROUP, IDLE); + break; + } +} + + +static int wpa_gtk_update(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + int ret = 0; + + os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN); + inc_byte_array(group->Counter, WPA_NONCE_LEN); + if (wpa_gmk_to_gtk(group->GMK, "Group key expansion", + wpa_auth->addr, group->GNonce, + group->GTK[group->GN - 1], group->GTK_len) < 0) + ret = -1; + wpa_hexdump_key(MSG_DEBUG, "GTK", + group->GTK[group->GN - 1], group->GTK_len); + +#ifdef CONFIG_IEEE80211W + if (wpa_auth->conf.ieee80211w != NO_MGMT_FRAME_PROTECTION) { + size_t len; + len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN); + inc_byte_array(group->Counter, WPA_NONCE_LEN); + if (wpa_gmk_to_gtk(group->GMK, "IGTK key expansion", + wpa_auth->addr, group->GNonce, + group->IGTK[group->GN_igtk - 4], len) < 0) + ret = -1; + wpa_hexdump_key(MSG_DEBUG, "IGTK", + group->IGTK[group->GN_igtk - 4], len); + } +#endif /* CONFIG_IEEE80211W */ + + return ret; +} + + +static void wpa_group_gtk_init(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + wpa_printf(MSG_DEBUG, "WPA: group state machine entering state " + "GTK_INIT (VLAN-ID %d)", group->vlan_id); + group->changed = FALSE; /* GInit is not cleared here; avoid loop */ + group->wpa_group_state = WPA_GROUP_GTK_INIT; + + /* GTK[0..N] = 0 */ + os_memset(group->GTK, 0, sizeof(group->GTK)); + group->GN = 1; + group->GM = 2; +#ifdef CONFIG_IEEE80211W + group->GN_igtk = 4; + group->GM_igtk = 5; +#endif /* CONFIG_IEEE80211W */ + /* GTK[GN] = CalcGTK() */ + wpa_gtk_update(wpa_auth, group); +} + + +static int wpa_group_update_sta(struct wpa_state_machine *sm, void *ctx) +{ + if (ctx != NULL && ctx != sm->group) + return 0; + + if (sm->wpa_ptk_state != WPA_PTK_PTKINITDONE) { + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "Not in PTKINITDONE; skip Group Key update"); + sm->GUpdateStationKeys = FALSE; + return 0; + } + if (sm->GUpdateStationKeys) { + /* + * This should not really happen, so add a debug log entry. + * Since we clear the GKeyDoneStations before the loop, the + * station needs to be counted here anyway. + */ + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, + "GUpdateStationKeys was already set when " + "marking station for GTK rekeying"); + } + + /* Do not rekey GTK/IGTK when STA is in WNM-Sleep Mode */ + if (sm->is_wnmsleep) + return 0; + + sm->group->GKeyDoneStations++; + sm->GUpdateStationKeys = TRUE; + + wpa_sm_step(sm); + return 0; +} + + +#ifdef CONFIG_WNM +/* update GTK when exiting WNM-Sleep Mode */ +void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm) +{ + if (sm == NULL || sm->is_wnmsleep) + return; + + wpa_group_update_sta(sm, NULL); +} + + +void wpa_set_wnmsleep(struct wpa_state_machine *sm, int flag) +{ + if (sm) + sm->is_wnmsleep = !!flag; +} + + +int wpa_wnmsleep_gtk_subelem(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_group *gsm = sm->group; + u8 *start = pos; + + /* + * GTK subelement: + * Sub-elem ID[1] | Length[1] | Key Info[2] | Key Length[1] | RSC[8] | + * Key[5..32] + */ + *pos++ = WNM_SLEEP_SUBELEM_GTK; + *pos++ = 11 + gsm->GTK_len; + /* Key ID in B0-B1 of Key Info */ + WPA_PUT_LE16(pos, gsm->GN & 0x03); + pos += 2; + *pos++ = gsm->GTK_len; + if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, pos) != 0) + return 0; + pos += 8; + os_memcpy(pos, gsm->GTK[gsm->GN - 1], gsm->GTK_len); + pos += gsm->GTK_len; + + wpa_printf(MSG_DEBUG, "WNM: GTK Key ID %u in WNM-Sleep Mode exit", + gsm->GN); + wpa_hexdump_key(MSG_DEBUG, "WNM: GTK in WNM-Sleep Mode exit", + gsm->GTK[gsm->GN - 1], gsm->GTK_len); + + return pos - start; +} + + +#ifdef CONFIG_IEEE80211W +int wpa_wnmsleep_igtk_subelem(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_group *gsm = sm->group; + u8 *start = pos; + size_t len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher); + + /* + * IGTK subelement: + * Sub-elem ID[1] | Length[1] | KeyID[2] | PN[6] | Key[16] + */ + *pos++ = WNM_SLEEP_SUBELEM_IGTK; + *pos++ = 2 + 6 + len; + WPA_PUT_LE16(pos, gsm->GN_igtk); + pos += 2; + if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, pos) != 0) + return 0; + pos += 6; + + os_memcpy(pos, gsm->IGTK[gsm->GN_igtk - 4], len); + pos += len; + + wpa_printf(MSG_DEBUG, "WNM: IGTK Key ID %u in WNM-Sleep Mode exit", + gsm->GN_igtk); + wpa_hexdump_key(MSG_DEBUG, "WNM: IGTK in WNM-Sleep Mode exit", + gsm->IGTK[gsm->GN_igtk - 4], len); + + return pos - start; +} +#endif /* CONFIG_IEEE80211W */ +#endif /* CONFIG_WNM */ + + +static void wpa_group_setkeys(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + int tmp; + + wpa_printf(MSG_DEBUG, "WPA: group state machine entering state " + "SETKEYS (VLAN-ID %d)", group->vlan_id); + group->changed = TRUE; + group->wpa_group_state = WPA_GROUP_SETKEYS; + group->GTKReKey = FALSE; + tmp = group->GM; + group->GM = group->GN; + group->GN = tmp; +#ifdef CONFIG_IEEE80211W + tmp = group->GM_igtk; + group->GM_igtk = group->GN_igtk; + group->GN_igtk = tmp; +#endif /* CONFIG_IEEE80211W */ + /* "GKeyDoneStations = GNoStations" is done in more robust way by + * counting the STAs that are marked with GUpdateStationKeys instead of + * including all STAs that could be in not-yet-completed state. */ + wpa_gtk_update(wpa_auth, group); + + if (group->GKeyDoneStations) { + wpa_printf(MSG_DEBUG, "wpa_group_setkeys: Unexpected " + "GKeyDoneStations=%d when starting new GTK rekey", + group->GKeyDoneStations); + group->GKeyDoneStations = 0; + } + wpa_auth_for_each_sta(wpa_auth, wpa_group_update_sta, group); + wpa_printf(MSG_DEBUG, "wpa_group_setkeys: GKeyDoneStations=%d", + group->GKeyDoneStations); +} + + +static int wpa_group_config_group_keys(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + int ret = 0; + + if (wpa_auth_set_key(wpa_auth, group->vlan_id, + wpa_cipher_to_alg(wpa_auth->conf.wpa_group), + broadcast_ether_addr, group->GN, + group->GTK[group->GN - 1], group->GTK_len) < 0) + ret = -1; + +#ifdef CONFIG_IEEE80211W + if (wpa_auth->conf.ieee80211w != NO_MGMT_FRAME_PROTECTION) { + enum wpa_alg alg; + size_t len; + + alg = wpa_cipher_to_alg(wpa_auth->conf.group_mgmt_cipher); + len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + + if (ret == 0 && + wpa_auth_set_key(wpa_auth, group->vlan_id, alg, + broadcast_ether_addr, group->GN_igtk, + group->IGTK[group->GN_igtk - 4], len) < 0) + ret = -1; + } +#endif /* CONFIG_IEEE80211W */ + + return ret; +} + + +static int wpa_group_disconnect_cb(struct wpa_state_machine *sm, void *ctx) +{ + if (sm->group == ctx) { + wpa_printf(MSG_DEBUG, "WPA: Mark STA " MACSTR + " for discconnection due to fatal failure", + MAC2STR(sm->addr)); + sm->Disconnect = TRUE; + } + + return 0; +} + + +static void wpa_group_fatal_failure(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + wpa_printf(MSG_DEBUG, "WPA: group state machine entering state FATAL_FAILURE"); + group->changed = TRUE; + group->wpa_group_state = WPA_GROUP_FATAL_FAILURE; + wpa_auth_for_each_sta(wpa_auth, wpa_group_disconnect_cb, group); +} + + +static int wpa_group_setkeysdone(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + wpa_printf(MSG_DEBUG, "WPA: group state machine entering state " + "SETKEYSDONE (VLAN-ID %d)", group->vlan_id); + group->changed = TRUE; + group->wpa_group_state = WPA_GROUP_SETKEYSDONE; + + if (wpa_group_config_group_keys(wpa_auth, group) < 0) { + wpa_group_fatal_failure(wpa_auth, group); + return -1; + } + + return 0; +} + + +static void wpa_group_sm_step(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + if (group->GInit) { + wpa_group_gtk_init(wpa_auth, group); + } else if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) { + /* Do not allow group operations */ + } else if (group->wpa_group_state == WPA_GROUP_GTK_INIT && + group->GTKAuthenticator) { + wpa_group_setkeysdone(wpa_auth, group); + } else if (group->wpa_group_state == WPA_GROUP_SETKEYSDONE && + group->GTKReKey) { + wpa_group_setkeys(wpa_auth, group); + } else if (group->wpa_group_state == WPA_GROUP_SETKEYS) { + if (group->GKeyDoneStations == 0) + wpa_group_setkeysdone(wpa_auth, group); + else if (group->GTKReKey) + wpa_group_setkeys(wpa_auth, group); + } +} + + +static int wpa_sm_step(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return 0; + + if (sm->in_step_loop) { + /* This should not happen, but if it does, make sure we do not + * end up freeing the state machine too early by exiting the + * recursive call. */ + wpa_printf(MSG_ERROR, "WPA: wpa_sm_step() called recursively"); + return 0; + } + + sm->in_step_loop = 1; + do { + if (sm->pending_deinit) + break; + + sm->changed = FALSE; + sm->wpa_auth->group->changed = FALSE; + + SM_STEP_RUN(WPA_PTK); + if (sm->pending_deinit) + break; + SM_STEP_RUN(WPA_PTK_GROUP); + if (sm->pending_deinit) + break; + wpa_group_sm_step(sm->wpa_auth, sm->group); + } while (sm->changed || sm->wpa_auth->group->changed); + sm->in_step_loop = 0; + + if (sm->pending_deinit) { + wpa_printf(MSG_DEBUG, "WPA: Completing pending STA state " + "machine deinit for " MACSTR, MAC2STR(sm->addr)); + wpa_free_sta_sm(sm); + return 1; + } + return 0; +} + + +static void wpa_sm_call_step(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_state_machine *sm = eloop_ctx; + wpa_sm_step(sm); +} + + +void wpa_auth_sm_notify(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return; + eloop_register_timeout(0, 0, wpa_sm_call_step, sm, NULL); +} + + +void wpa_gtk_rekey(struct wpa_authenticator *wpa_auth) +{ + int tmp, i; + struct wpa_group *group; + + if (wpa_auth == NULL) + return; + + group = wpa_auth->group; + + for (i = 0; i < 2; i++) { + tmp = group->GM; + group->GM = group->GN; + group->GN = tmp; +#ifdef CONFIG_IEEE80211W + tmp = group->GM_igtk; + group->GM_igtk = group->GN_igtk; + group->GN_igtk = tmp; +#endif /* CONFIG_IEEE80211W */ + wpa_gtk_update(wpa_auth, group); + wpa_group_config_group_keys(wpa_auth, group); + } +} + + +static const char * wpa_bool_txt(int val) +{ + return val ? "TRUE" : "FALSE"; +} + + +#define RSN_SUITE "%02x-%02x-%02x-%d" +#define RSN_SUITE_ARG(s) \ +((s) >> 24) & 0xff, ((s) >> 16) & 0xff, ((s) >> 8) & 0xff, (s) & 0xff + +int wpa_get_mib(struct wpa_authenticator *wpa_auth, char *buf, size_t buflen) +{ + int len = 0, ret; + char pmkid_txt[PMKID_LEN * 2 + 1]; +#ifdef CONFIG_RSN_PREAUTH + const int preauth = 1; +#else /* CONFIG_RSN_PREAUTH */ + const int preauth = 0; +#endif /* CONFIG_RSN_PREAUTH */ + + if (wpa_auth == NULL) + return len; + + ret = os_snprintf(buf + len, buflen - len, + "dot11RSNAOptionImplemented=TRUE\n" + "dot11RSNAPreauthenticationImplemented=%s\n" + "dot11RSNAEnabled=%s\n" + "dot11RSNAPreauthenticationEnabled=%s\n", + wpa_bool_txt(preauth), + wpa_bool_txt(wpa_auth->conf.wpa & WPA_PROTO_RSN), + wpa_bool_txt(wpa_auth->conf.rsn_preauth)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + wpa_snprintf_hex(pmkid_txt, sizeof(pmkid_txt), + wpa_auth->dot11RSNAPMKIDUsed, PMKID_LEN); + + ret = os_snprintf( + buf + len, buflen - len, + "dot11RSNAConfigVersion=%u\n" + "dot11RSNAConfigPairwiseKeysSupported=9999\n" + /* FIX: dot11RSNAConfigGroupCipher */ + /* FIX: dot11RSNAConfigGroupRekeyMethod */ + /* FIX: dot11RSNAConfigGroupRekeyTime */ + /* FIX: dot11RSNAConfigGroupRekeyPackets */ + "dot11RSNAConfigGroupRekeyStrict=%u\n" + "dot11RSNAConfigGroupUpdateCount=%u\n" + "dot11RSNAConfigPairwiseUpdateCount=%u\n" + "dot11RSNAConfigGroupCipherSize=%u\n" + "dot11RSNAConfigPMKLifetime=%u\n" + "dot11RSNAConfigPMKReauthThreshold=%u\n" + "dot11RSNAConfigNumberOfPTKSAReplayCounters=0\n" + "dot11RSNAConfigSATimeout=%u\n" + "dot11RSNAAuthenticationSuiteSelected=" RSN_SUITE "\n" + "dot11RSNAPairwiseCipherSelected=" RSN_SUITE "\n" + "dot11RSNAGroupCipherSelected=" RSN_SUITE "\n" + "dot11RSNAPMKIDUsed=%s\n" + "dot11RSNAAuthenticationSuiteRequested=" RSN_SUITE "\n" + "dot11RSNAPairwiseCipherRequested=" RSN_SUITE "\n" + "dot11RSNAGroupCipherRequested=" RSN_SUITE "\n" + "dot11RSNATKIPCounterMeasuresInvoked=%u\n" + "dot11RSNA4WayHandshakeFailures=%u\n" + "dot11RSNAConfigNumberOfGTKSAReplayCounters=0\n", + RSN_VERSION, + !!wpa_auth->conf.wpa_strict_rekey, + dot11RSNAConfigGroupUpdateCount, + dot11RSNAConfigPairwiseUpdateCount, + wpa_cipher_key_len(wpa_auth->conf.wpa_group) * 8, + dot11RSNAConfigPMKLifetime, + dot11RSNAConfigPMKReauthThreshold, + dot11RSNAConfigSATimeout, + RSN_SUITE_ARG(wpa_auth->dot11RSNAAuthenticationSuiteSelected), + RSN_SUITE_ARG(wpa_auth->dot11RSNAPairwiseCipherSelected), + RSN_SUITE_ARG(wpa_auth->dot11RSNAGroupCipherSelected), + pmkid_txt, + RSN_SUITE_ARG(wpa_auth->dot11RSNAAuthenticationSuiteRequested), + RSN_SUITE_ARG(wpa_auth->dot11RSNAPairwiseCipherRequested), + RSN_SUITE_ARG(wpa_auth->dot11RSNAGroupCipherRequested), + wpa_auth->dot11RSNATKIPCounterMeasuresInvoked, + wpa_auth->dot11RSNA4WayHandshakeFailures); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* TODO: dot11RSNAConfigPairwiseCiphersTable */ + /* TODO: dot11RSNAConfigAuthenticationSuitesTable */ + + /* Private MIB */ + ret = os_snprintf(buf + len, buflen - len, "hostapdWPAGroupState=%d\n", + wpa_auth->group->wpa_group_state); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + return len; +} + + +int wpa_get_mib_sta(struct wpa_state_machine *sm, char *buf, size_t buflen) +{ + int len = 0, ret; + u32 pairwise = 0; + + if (sm == NULL) + return 0; + + /* TODO: FF-FF-FF-FF-FF-FF entry for broadcast/multicast stats */ + + /* dot11RSNAStatsEntry */ + + pairwise = wpa_cipher_to_suite(sm->wpa == WPA_VERSION_WPA2 ? + WPA_PROTO_RSN : WPA_PROTO_WPA, + sm->pairwise); + if (pairwise == 0) + return 0; + + ret = os_snprintf( + buf + len, buflen - len, + /* TODO: dot11RSNAStatsIndex */ + "dot11RSNAStatsSTAAddress=" MACSTR "\n" + "dot11RSNAStatsVersion=1\n" + "dot11RSNAStatsSelectedPairwiseCipher=" RSN_SUITE "\n" + /* TODO: dot11RSNAStatsTKIPICVErrors */ + "dot11RSNAStatsTKIPLocalMICFailures=%u\n" + "dot11RSNAStatsTKIPRemoteMICFailures=%u\n" + /* TODO: dot11RSNAStatsCCMPReplays */ + /* TODO: dot11RSNAStatsCCMPDecryptErrors */ + /* TODO: dot11RSNAStatsTKIPReplays */, + MAC2STR(sm->addr), + RSN_SUITE_ARG(pairwise), + sm->dot11RSNAStatsTKIPLocalMICFailures, + sm->dot11RSNAStatsTKIPRemoteMICFailures); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* Private MIB */ + ret = os_snprintf(buf + len, buflen - len, + "hostapdWPAPTKState=%d\n" + "hostapdWPAPTKGroupState=%d\n", + sm->wpa_ptk_state, + sm->wpa_ptk_group_state); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + return len; +} + + +void wpa_auth_countermeasures_start(struct wpa_authenticator *wpa_auth) +{ + if (wpa_auth) + wpa_auth->dot11RSNATKIPCounterMeasuresInvoked++; +} + + +int wpa_auth_pairwise_set(struct wpa_state_machine *sm) +{ + return sm && sm->pairwise_set; +} + + +int wpa_auth_get_pairwise(struct wpa_state_machine *sm) +{ + return sm->pairwise; +} + + +int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return -1; + return sm->wpa_key_mgmt; +} + + +int wpa_auth_sta_wpa_version(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return 0; + return sm->wpa; +} + + +int wpa_auth_sta_clear_pmksa(struct wpa_state_machine *sm, + struct rsn_pmksa_cache_entry *entry) +{ + if (sm == NULL || sm->pmksa != entry) + return -1; + sm->pmksa = NULL; + return 0; +} + + +struct rsn_pmksa_cache_entry * +wpa_auth_sta_get_pmksa(struct wpa_state_machine *sm) +{ + return sm ? sm->pmksa : NULL; +} + + +void wpa_auth_sta_local_mic_failure_report(struct wpa_state_machine *sm) +{ + if (sm) + sm->dot11RSNAStatsTKIPLocalMICFailures++; +} + + +const u8 * wpa_auth_get_wpa_ie(struct wpa_authenticator *wpa_auth, size_t *len) +{ + if (wpa_auth == NULL) + return NULL; + *len = wpa_auth->wpa_ie_len; + return wpa_auth->wpa_ie; +} + + +int wpa_auth_pmksa_add(struct wpa_state_machine *sm, const u8 *pmk, + int session_timeout, struct eapol_state_machine *eapol) +{ + if (sm == NULL || sm->wpa != WPA_VERSION_WPA2 || + sm->wpa_auth->conf.disable_pmksa_caching) + return -1; + + if (pmksa_cache_auth_add(sm->wpa_auth->pmksa, pmk, PMK_LEN, + sm->PTK.kck, sm->PTK.kck_len, + sm->wpa_auth->addr, sm->addr, session_timeout, + eapol, sm->wpa_key_mgmt)) + return 0; + + return -1; +} + + +int wpa_auth_pmksa_add_preauth(struct wpa_authenticator *wpa_auth, + const u8 *pmk, size_t len, const u8 *sta_addr, + int session_timeout, + struct eapol_state_machine *eapol) +{ + if (wpa_auth == NULL) + return -1; + + if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, len, + NULL, 0, + wpa_auth->addr, + sta_addr, session_timeout, eapol, + WPA_KEY_MGMT_IEEE8021X)) + return 0; + + return -1; +} + + +int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *pmk) +{ + if (wpa_auth->conf.disable_pmksa_caching) + return -1; + + if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, PMK_LEN, + NULL, 0, + wpa_auth->addr, addr, 0, NULL, + WPA_KEY_MGMT_SAE)) + return 0; + + return -1; +} + + +void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr) +{ + struct rsn_pmksa_cache_entry *pmksa; + + if (wpa_auth == NULL || wpa_auth->pmksa == NULL) + return; + pmksa = pmksa_cache_auth_get(wpa_auth->pmksa, sta_addr, NULL); + if (pmksa) { + wpa_printf(MSG_DEBUG, "WPA: Remove PMKSA cache entry for " + MACSTR " based on request", MAC2STR(sta_addr)); + pmksa_cache_free_entry(wpa_auth->pmksa, pmksa); + } +} + + +/* + * Remove and free the group from wpa_authenticator. This is triggered by a + * callback to make sure nobody is currently iterating the group list while it + * gets modified. + */ +static void wpa_group_free(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + struct wpa_group *prev = wpa_auth->group; + + wpa_printf(MSG_DEBUG, "WPA: Remove group state machine for VLAN-ID %d", + group->vlan_id); + + while (prev) { + if (prev->next == group) { + /* This never frees the special first group as needed */ + prev->next = group->next; + os_free(group); + break; + } + prev = prev->next; + } + +} + + +/* Increase the reference counter for group */ +static void wpa_group_get(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + /* Skip the special first group */ + if (wpa_auth->group == group) + return; + + group->references++; +} + + +/* Decrease the reference counter and maybe free the group */ +static void wpa_group_put(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + /* Skip the special first group */ + if (wpa_auth->group == group) + return; + + group->references--; + if (group->references) + return; + wpa_group_free(wpa_auth, group); +} + + +/* + * Add a group that has its references counter set to zero. Caller needs to + * call wpa_group_get() on the return value to mark the entry in use. + */ +static struct wpa_group * +wpa_auth_add_group(struct wpa_authenticator *wpa_auth, int vlan_id) +{ + struct wpa_group *group; + + if (wpa_auth == NULL || wpa_auth->group == NULL) + return NULL; + + wpa_printf(MSG_DEBUG, "WPA: Add group state machine for VLAN-ID %d", + vlan_id); + group = wpa_group_init(wpa_auth, vlan_id, 0); + if (group == NULL) + return NULL; + + group->next = wpa_auth->group->next; + wpa_auth->group->next = group; + + return group; +} + + +int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id) +{ + struct wpa_group *group; + + if (sm == NULL || sm->wpa_auth == NULL) + return 0; + + group = sm->wpa_auth->group; + while (group) { + if (group->vlan_id == vlan_id) + break; + group = group->next; + } + + if (group == NULL) { + group = wpa_auth_add_group(sm->wpa_auth, vlan_id); + if (group == NULL) + return -1; + } + + if (sm->group == group) + return 0; + + if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + return -1; + + wpa_printf(MSG_DEBUG, "WPA: Moving STA " MACSTR " to use group state " + "machine for VLAN ID %d", MAC2STR(sm->addr), vlan_id); + + wpa_group_get(sm->wpa_auth, group); + wpa_group_put(sm->wpa_auth, sm->group); + sm->group = group; + + return 0; +} + + +void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int ack) +{ + if (wpa_auth == NULL || sm == NULL) + return; + wpa_printf(MSG_DEBUG, "WPA: EAPOL-Key TX status for STA " MACSTR + " ack=%d", MAC2STR(sm->addr), ack); + if (sm->pending_1_of_4_timeout && ack) { + /* + * Some deployed supplicant implementations update their SNonce + * for each EAPOL-Key 2/4 message even within the same 4-way + * handshake and then fail to use the first SNonce when + * deriving the PTK. This results in unsuccessful 4-way + * handshake whenever the relatively short initial timeout is + * reached and EAPOL-Key 1/4 is retransmitted. Try to work + * around this by increasing the timeout now that we know that + * the station has received the frame. + */ + int timeout_ms = eapol_key_timeout_subseq; + wpa_printf(MSG_DEBUG, "WPA: Increase initial EAPOL-Key 1/4 " + "timeout by %u ms because of acknowledged frame", + timeout_ms); + eloop_cancel_timeout(wpa_send_eapol_timeout, wpa_auth, sm); + eloop_register_timeout(timeout_ms / 1000, + (timeout_ms % 1000) * 1000, + wpa_send_eapol_timeout, wpa_auth, sm); + } +} + + +int wpa_auth_uses_sae(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return 0; + return wpa_key_mgmt_sae(sm->wpa_key_mgmt); +} + + +int wpa_auth_uses_ft_sae(struct wpa_state_machine *sm) +{ + if (sm == NULL) + return 0; + return sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE; +} + + +#ifdef CONFIG_P2P +int wpa_auth_get_ip_addr(struct wpa_state_machine *sm, u8 *addr) +{ + if (sm == NULL || WPA_GET_BE32(sm->ip_addr) == 0) + return -1; + os_memcpy(addr, sm->ip_addr, 4); + return 0; +} +#endif /* CONFIG_P2P */ + + +int wpa_auth_radius_das_disconnect_pmksa(struct wpa_authenticator *wpa_auth, + struct radius_das_attrs *attr) +{ + return pmksa_cache_auth_radius_das_disconnect(wpa_auth->pmksa, attr); +} + + +void wpa_auth_reconfig_group_keys(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group; + + if (!wpa_auth) + return; + for (group = wpa_auth->group; group; group = group->next) + wpa_group_config_group_keys(wpa_auth, group); +} diff --git a/freebsd/contrib/wpa/src/ap/wpa_auth.h b/freebsd/contrib/wpa/src/ap/wpa_auth.h new file mode 100644 index 00000000..fd04f169 --- /dev/null +++ b/freebsd/contrib/wpa/src/ap/wpa_auth.h @@ -0,0 +1,328 @@ +/* + * hostapd - IEEE 802.11i-2004 / WPA Authenticator + * Copyright (c) 2004-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_H +#define WPA_AUTH_H + +#include "common/defs.h" +#include "common/eapol_common.h" +#include "common/wpa_common.h" +#include "common/ieee802_11_defs.h" + +#define MAX_OWN_IE_OVERRIDE 256 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif /* _MSC_VER */ + +/* IEEE Std 802.11r-2008, 11A.10.3 - Remote request/response frame definition + */ +struct ft_rrb_frame { + u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */ + u8 packet_type; /* FT_PACKET_REQUEST/FT_PACKET_RESPONSE */ + le16 action_length; /* little endian length of action_frame */ + u8 ap_address[ETH_ALEN]; + /* + * Followed by action_length bytes of FT Action frame (from Category + * field to the end of Action Frame body. + */ +} STRUCT_PACKED; + +#define RSN_REMOTE_FRAME_TYPE_FT_RRB 1 + +#define FT_PACKET_REQUEST 0 +#define FT_PACKET_RESPONSE 1 +/* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r */ +#define FT_PACKET_R0KH_R1KH_PULL 200 +#define FT_PACKET_R0KH_R1KH_RESP 201 +#define FT_PACKET_R0KH_R1KH_PUSH 202 + +#define FT_R0KH_R1KH_PULL_DATA_LEN 44 +#define FT_R0KH_R1KH_RESP_DATA_LEN 76 +#define FT_R0KH_R1KH_PUSH_DATA_LEN 88 +#define FT_R0KH_R1KH_PULL_NONCE_LEN 16 + +struct ft_r0kh_r1kh_pull_frame { + u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */ + u8 packet_type; /* FT_PACKET_R0KH_R1KH_PULL */ + le16 data_length; /* little endian length of data (44) */ + u8 ap_address[ETH_ALEN]; + + u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; + u8 pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 r1kh_id[FT_R1KH_ID_LEN]; + u8 s1kh_id[ETH_ALEN]; + u8 pad[4]; /* 8-octet boundary for AES key wrap */ + u8 key_wrap_extra[8]; +} STRUCT_PACKED; + +struct ft_r0kh_r1kh_resp_frame { + u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */ + u8 packet_type; /* FT_PACKET_R0KH_R1KH_RESP */ + le16 data_length; /* little endian length of data (76) */ + u8 ap_address[ETH_ALEN]; + + u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; /* copied from pull */ + u8 r1kh_id[FT_R1KH_ID_LEN]; /* copied from pull */ + u8 s1kh_id[ETH_ALEN]; /* copied from pull */ + u8 pmk_r1[PMK_LEN]; + u8 pmk_r1_name[WPA_PMK_NAME_LEN]; + le16 pairwise; + u8 pad[2]; /* 8-octet boundary for AES key wrap */ + u8 key_wrap_extra[8]; +} STRUCT_PACKED; + +struct ft_r0kh_r1kh_push_frame { + u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */ + u8 packet_type; /* FT_PACKET_R0KH_R1KH_PUSH */ + le16 data_length; /* little endian length of data (88) */ + u8 ap_address[ETH_ALEN]; + + /* Encrypted with AES key-wrap */ + u8 timestamp[4]; /* current time in seconds since unix epoch, little + * endian */ + u8 r1kh_id[FT_R1KH_ID_LEN]; + u8 s1kh_id[ETH_ALEN]; + u8 pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 pmk_r1[PMK_LEN]; + u8 pmk_r1_name[WPA_PMK_NAME_LEN]; + le16 pairwise; + u8 pad[6]; /* 8-octet boundary for AES key wrap */ + u8 key_wrap_extra[8]; +} STRUCT_PACKED; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif /* _MSC_VER */ + + +/* per STA state machine data */ + +struct wpa_authenticator; +struct wpa_state_machine; +struct rsn_pmksa_cache_entry; +struct eapol_state_machine; + + +struct ft_remote_r0kh { + struct ft_remote_r0kh *next; + u8 addr[ETH_ALEN]; + u8 id[FT_R0KH_ID_MAX_LEN]; + size_t id_len; + u8 key[16]; +}; + + +struct ft_remote_r1kh { + struct ft_remote_r1kh *next; + u8 addr[ETH_ALEN]; + u8 id[FT_R1KH_ID_LEN]; + u8 key[16]; +}; + + +struct wpa_auth_config { + int wpa; + int wpa_key_mgmt; + int wpa_pairwise; + int wpa_group; + int wpa_group_rekey; + int wpa_strict_rekey; + int wpa_gmk_rekey; + int wpa_ptk_rekey; + int rsn_pairwise; + int rsn_preauth; + int eapol_version; + int peerkey; + int wmm_enabled; + int wmm_uapsd; + int disable_pmksa_caching; + int okc; + int tx_status; +#ifdef CONFIG_IEEE80211W + enum mfp_options ieee80211w; + int group_mgmt_cipher; +#endif /* CONFIG_IEEE80211W */ +#ifdef CONFIG_IEEE80211R + u8 ssid[SSID_MAX_LEN]; + size_t ssid_len; + u8 mobility_domain[MOBILITY_DOMAIN_ID_LEN]; + u8 r0_key_holder[FT_R0KH_ID_MAX_LEN]; + size_t r0_key_holder_len; + u8 r1_key_holder[FT_R1KH_ID_LEN]; + u32 r0_key_lifetime; + u32 reassociation_deadline; + struct ft_remote_r0kh *r0kh_list; + struct ft_remote_r1kh *r1kh_list; + int pmk_r1_push; + int ft_over_ds; +#endif /* CONFIG_IEEE80211R */ + int disable_gtk; + int ap_mlme; +#ifdef CONFIG_TESTING_OPTIONS + double corrupt_gtk_rekey_mic_probability; + u8 own_ie_override[MAX_OWN_IE_OVERRIDE]; + size_t own_ie_override_len; +#endif /* CONFIG_TESTING_OPTIONS */ +#ifdef CONFIG_P2P + u8 ip_addr_go[4]; + u8 ip_addr_mask[4]; + u8 ip_addr_start[4]; + u8 ip_addr_end[4]; +#endif /* CONFIG_P2P */ +}; + +typedef enum { + LOGGER_DEBUG, LOGGER_INFO, LOGGER_WARNING +} logger_level; + +typedef enum { + WPA_EAPOL_portEnabled, WPA_EAPOL_portValid, WPA_EAPOL_authorized, + WPA_EAPOL_portControl_Auto, WPA_EAPOL_keyRun, WPA_EAPOL_keyAvailable, + WPA_EAPOL_keyDone, WPA_EAPOL_inc_EapolFramesTx +} wpa_eapol_variable; + +struct wpa_auth_callbacks { + void *ctx; + void (*logger)(void *ctx, const u8 *addr, logger_level level, + const char *txt); + void (*disconnect)(void *ctx, const u8 *addr, u16 reason); + int (*mic_failure_report)(void *ctx, const u8 *addr); + void (*psk_failure_report)(void *ctx, const u8 *addr); + void (*set_eapol)(void *ctx, const u8 *addr, wpa_eapol_variable var, + int value); + int (*get_eapol)(void *ctx, const u8 *addr, wpa_eapol_variable var); + const u8 * (*get_psk)(void *ctx, const u8 *addr, const u8 *p2p_dev_addr, + const u8 *prev_psk); + int (*get_msk)(void *ctx, const u8 *addr, u8 *msk, size_t *len); + int (*set_key)(void *ctx, int vlan_id, enum wpa_alg alg, + const u8 *addr, int idx, u8 *key, size_t key_len); + int (*get_seqnum)(void *ctx, const u8 *addr, int idx, u8 *seq); + int (*send_eapol)(void *ctx, const u8 *addr, const u8 *data, + size_t data_len, int encrypt); + int (*for_each_sta)(void *ctx, int (*cb)(struct wpa_state_machine *sm, + void *ctx), void *cb_ctx); + int (*for_each_auth)(void *ctx, int (*cb)(struct wpa_authenticator *a, + void *ctx), void *cb_ctx); + int (*send_ether)(void *ctx, const u8 *dst, u16 proto, const u8 *data, + size_t data_len); +#ifdef CONFIG_IEEE80211R + struct wpa_state_machine * (*add_sta)(void *ctx, const u8 *sta_addr); + int (*send_ft_action)(void *ctx, const u8 *dst, + const u8 *data, size_t data_len); + int (*add_tspec)(void *ctx, const u8 *sta_addr, u8 *tspec_ie, + size_t tspec_ielen); +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_MESH + int (*start_ampe)(void *ctx, const u8 *sta_addr); +#endif /* CONFIG_MESH */ +}; + +struct wpa_authenticator * wpa_init(const u8 *addr, + struct wpa_auth_config *conf, + struct wpa_auth_callbacks *cb); +int wpa_init_keys(struct wpa_authenticator *wpa_auth); +void wpa_deinit(struct wpa_authenticator *wpa_auth); +int wpa_reconfig(struct wpa_authenticator *wpa_auth, + struct wpa_auth_config *conf); + +enum { + WPA_IE_OK, WPA_INVALID_IE, WPA_INVALID_GROUP, WPA_INVALID_PAIRWISE, + WPA_INVALID_AKMP, WPA_NOT_ENABLED, WPA_ALLOC_FAIL, + WPA_MGMT_FRAME_PROTECTION_VIOLATION, WPA_INVALID_MGMT_GROUP_CIPHER, + WPA_INVALID_MDIE, WPA_INVALID_PROTO +}; + +int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *wpa_ie, size_t wpa_ie_len, + const u8 *mdie, size_t mdie_len); +int wpa_validate_osen(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *osen_ie, size_t osen_ie_len); +int wpa_auth_uses_mfp(struct wpa_state_machine *sm); +struct wpa_state_machine * +wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *p2p_dev_addr); +int wpa_auth_sta_associated(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm); +void wpa_auth_sta_no_wpa(struct wpa_state_machine *sm); +void wpa_auth_sta_deinit(struct wpa_state_machine *sm); +void wpa_receive(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + u8 *data, size_t data_len); +enum wpa_event { + WPA_AUTH, WPA_ASSOC, WPA_DISASSOC, WPA_DEAUTH, WPA_REAUTH, + WPA_REAUTH_EAPOL, WPA_ASSOC_FT +}; +void wpa_remove_ptk(struct wpa_state_machine *sm); +int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event); +void wpa_auth_sm_notify(struct wpa_state_machine *sm); +void wpa_gtk_rekey(struct wpa_authenticator *wpa_auth); +int wpa_get_mib(struct wpa_authenticator *wpa_auth, char *buf, size_t buflen); +int wpa_get_mib_sta(struct wpa_state_machine *sm, char *buf, size_t buflen); +void wpa_auth_countermeasures_start(struct wpa_authenticator *wpa_auth); +int wpa_auth_pairwise_set(struct wpa_state_machine *sm); +int wpa_auth_get_pairwise(struct wpa_state_machine *sm); +int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm); +int wpa_auth_sta_wpa_version(struct wpa_state_machine *sm); +int wpa_auth_sta_clear_pmksa(struct wpa_state_machine *sm, + struct rsn_pmksa_cache_entry *entry); +struct rsn_pmksa_cache_entry * +wpa_auth_sta_get_pmksa(struct wpa_state_machine *sm); +void wpa_auth_sta_local_mic_failure_report(struct wpa_state_machine *sm); +const u8 * wpa_auth_get_wpa_ie(struct wpa_authenticator *wpa_auth, + size_t *len); +int wpa_auth_pmksa_add(struct wpa_state_machine *sm, const u8 *pmk, + int session_timeout, struct eapol_state_machine *eapol); +int wpa_auth_pmksa_add_preauth(struct wpa_authenticator *wpa_auth, + const u8 *pmk, size_t len, const u8 *sta_addr, + int session_timeout, + struct eapol_state_machine *eapol); +int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *pmk); +void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr); +int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id); +void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int ack); + +#ifdef CONFIG_IEEE80211R +u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos, + size_t max_len, int auth_alg, + const u8 *req_ies, size_t req_ies_len); +void wpa_ft_process_auth(struct wpa_state_machine *sm, const u8 *bssid, + u16 auth_transaction, const u8 *ies, size_t ies_len, + void (*cb)(void *ctx, const u8 *dst, const u8 *bssid, + u16 auth_transaction, u16 resp, + const u8 *ies, size_t ies_len), + void *ctx); +u16 wpa_ft_validate_reassoc(struct wpa_state_machine *sm, const u8 *ies, + size_t ies_len); +int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len); +int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, + const u8 *data, size_t data_len); +void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr); +#endif /* CONFIG_IEEE80211R */ + +void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm); +void wpa_set_wnmsleep(struct wpa_state_machine *sm, int flag); +int wpa_wnmsleep_gtk_subelem(struct wpa_state_machine *sm, u8 *pos); +int wpa_wnmsleep_igtk_subelem(struct wpa_state_machine *sm, u8 *pos); + +int wpa_auth_uses_sae(struct wpa_state_machine *sm); +int wpa_auth_uses_ft_sae(struct wpa_state_machine *sm); + +int wpa_auth_get_ip_addr(struct wpa_state_machine *sm, u8 *addr); + +struct radius_das_attrs; +int wpa_auth_radius_das_disconnect_pmksa(struct wpa_authenticator *wpa_auth, + struct radius_das_attrs *attr); +void wpa_auth_reconfig_group_keys(struct wpa_authenticator *wpa_auth); + +#endif /* WPA_AUTH_H */ diff --git a/freebsd/contrib/wpa/src/ap/wpa_auth_ft.c b/freebsd/contrib/wpa/src/ap/wpa_auth_ft.c new file mode 100644 index 00000000..32622b4d --- /dev/null +++ b/freebsd/contrib/wpa/src/ap/wpa_auth_ft.c @@ -0,0 +1,1794 @@ +#include + +/* + * hostapd - IEEE 802.11r - Fast BSS Transition + * Copyright (c) 2004-2015, 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 "utils/eloop.h" +#include "utils/list.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "crypto/aes_wrap.h" +#include "crypto/random.h" +#include "ap_config.h" +#include "ieee802_11.h" +#include "wmm.h" +#include "wpa_auth.h" +#include "wpa_auth_i.h" + + +#ifdef CONFIG_IEEE80211R + +static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm, + const u8 *current_ap, const u8 *sta_addr, + u16 status, const u8 *resp_ies, + size_t resp_ies_len); + + +static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst, + const u8 *data, size_t data_len) +{ + if (wpa_auth->cb.send_ether == NULL) + return -1; + wpa_printf(MSG_DEBUG, "FT: RRB send to " MACSTR, MAC2STR(dst)); + return wpa_auth->cb.send_ether(wpa_auth->cb.ctx, dst, ETH_P_RRB, + data, data_len); +} + + +static int wpa_ft_action_send(struct wpa_authenticator *wpa_auth, + const u8 *dst, const u8 *data, size_t data_len) +{ + if (wpa_auth->cb.send_ft_action == NULL) + return -1; + return wpa_auth->cb.send_ft_action(wpa_auth->cb.ctx, dst, + data, data_len); +} + + +static struct wpa_state_machine * +wpa_ft_add_sta(struct wpa_authenticator *wpa_auth, const u8 *sta_addr) +{ + if (wpa_auth->cb.add_sta == NULL) + return NULL; + return wpa_auth->cb.add_sta(wpa_auth->cb.ctx, sta_addr); +} + + +static int wpa_ft_add_tspec(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr, + u8 *tspec_ie, size_t tspec_ielen) +{ + if (wpa_auth->cb.add_tspec == NULL) { + wpa_printf(MSG_DEBUG, "FT: add_tspec is not initialized"); + return -1; + } + return wpa_auth->cb.add_tspec(wpa_auth->cb.ctx, sta_addr, tspec_ie, + tspec_ielen); +} + + +int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len) +{ + u8 *pos = buf; + u8 capab; + if (len < 2 + sizeof(struct rsn_mdie)) + return -1; + + *pos++ = WLAN_EID_MOBILITY_DOMAIN; + *pos++ = MOBILITY_DOMAIN_ID_LEN + 1; + os_memcpy(pos, conf->mobility_domain, MOBILITY_DOMAIN_ID_LEN); + pos += MOBILITY_DOMAIN_ID_LEN; + capab = 0; + if (conf->ft_over_ds) + capab |= RSN_FT_CAPAB_FT_OVER_DS; + *pos++ = capab; + + return pos - buf; +} + + +int wpa_write_ftie(struct wpa_auth_config *conf, const u8 *r0kh_id, + size_t r0kh_id_len, + const u8 *anonce, const u8 *snonce, + u8 *buf, size_t len, const u8 *subelem, + size_t subelem_len) +{ + u8 *pos = buf, *ielen; + struct rsn_ftie *hdr; + + if (len < 2 + sizeof(*hdr) + 2 + FT_R1KH_ID_LEN + 2 + r0kh_id_len + + subelem_len) + return -1; + + *pos++ = WLAN_EID_FAST_BSS_TRANSITION; + ielen = pos++; + + hdr = (struct rsn_ftie *) pos; + os_memset(hdr, 0, sizeof(*hdr)); + pos += sizeof(*hdr); + WPA_PUT_LE16(hdr->mic_control, 0); + if (anonce) + os_memcpy(hdr->anonce, anonce, WPA_NONCE_LEN); + if (snonce) + os_memcpy(hdr->snonce, snonce, WPA_NONCE_LEN); + + /* Optional Parameters */ + *pos++ = FTIE_SUBELEM_R1KH_ID; + *pos++ = FT_R1KH_ID_LEN; + os_memcpy(pos, conf->r1_key_holder, FT_R1KH_ID_LEN); + pos += FT_R1KH_ID_LEN; + + if (r0kh_id) { + *pos++ = FTIE_SUBELEM_R0KH_ID; + *pos++ = r0kh_id_len; + os_memcpy(pos, r0kh_id, r0kh_id_len); + pos += r0kh_id_len; + } + + if (subelem) { + os_memcpy(pos, subelem, subelem_len); + pos += subelem_len; + } + + *ielen = pos - buf - 2; + + return pos - buf; +} + + +struct wpa_ft_pmk_r0_sa { + struct wpa_ft_pmk_r0_sa *next; + u8 pmk_r0[PMK_LEN]; + u8 pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 spa[ETH_ALEN]; + int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */ + /* TODO: expiration, identity, radius_class, EAP type, VLAN ID */ + int pmk_r1_pushed; +}; + +struct wpa_ft_pmk_r1_sa { + struct wpa_ft_pmk_r1_sa *next; + u8 pmk_r1[PMK_LEN]; + u8 pmk_r1_name[WPA_PMK_NAME_LEN]; + u8 spa[ETH_ALEN]; + int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */ + /* TODO: expiration, identity, radius_class, EAP type, VLAN ID */ +}; + +struct wpa_ft_pmk_cache { + struct wpa_ft_pmk_r0_sa *pmk_r0; + struct wpa_ft_pmk_r1_sa *pmk_r1; +}; + +struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void) +{ + struct wpa_ft_pmk_cache *cache; + + cache = os_zalloc(sizeof(*cache)); + + return cache; +} + + +void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache) +{ + struct wpa_ft_pmk_r0_sa *r0, *r0prev; + struct wpa_ft_pmk_r1_sa *r1, *r1prev; + + r0 = cache->pmk_r0; + while (r0) { + r0prev = r0; + r0 = r0->next; + os_memset(r0prev->pmk_r0, 0, PMK_LEN); + os_free(r0prev); + } + + r1 = cache->pmk_r1; + while (r1) { + r1prev = r1; + r1 = r1->next; + os_memset(r1prev->pmk_r1, 0, PMK_LEN); + os_free(r1prev); + } + + os_free(cache); +} + + +static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r0, + const u8 *pmk_r0_name, int pairwise) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r0_sa *r0; + + /* TODO: add expiration and limit on number of entries in cache */ + + r0 = os_zalloc(sizeof(*r0)); + if (r0 == NULL) + return -1; + + os_memcpy(r0->pmk_r0, pmk_r0, PMK_LEN); + os_memcpy(r0->pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN); + os_memcpy(r0->spa, spa, ETH_ALEN); + r0->pairwise = pairwise; + + r0->next = cache->pmk_r0; + cache->pmk_r0 = r0; + + return 0; +} + + +static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r0_name, + u8 *pmk_r0, int *pairwise) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r0_sa *r0; + + r0 = cache->pmk_r0; + while (r0) { + if (os_memcmp(r0->spa, spa, ETH_ALEN) == 0 && + os_memcmp_const(r0->pmk_r0_name, pmk_r0_name, + WPA_PMK_NAME_LEN) == 0) { + os_memcpy(pmk_r0, r0->pmk_r0, PMK_LEN); + if (pairwise) + *pairwise = r0->pairwise; + return 0; + } + + r0 = r0->next; + } + + return -1; +} + + +static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r1, + const u8 *pmk_r1_name, int pairwise) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r1_sa *r1; + + /* TODO: add expiration and limit on number of entries in cache */ + + r1 = os_zalloc(sizeof(*r1)); + if (r1 == NULL) + return -1; + + os_memcpy(r1->pmk_r1, pmk_r1, PMK_LEN); + os_memcpy(r1->pmk_r1_name, pmk_r1_name, WPA_PMK_NAME_LEN); + os_memcpy(r1->spa, spa, ETH_ALEN); + r1->pairwise = pairwise; + + r1->next = cache->pmk_r1; + cache->pmk_r1 = r1; + + return 0; +} + + +static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r1_name, + u8 *pmk_r1, int *pairwise) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r1_sa *r1; + + r1 = cache->pmk_r1; + while (r1) { + if (os_memcmp(r1->spa, spa, ETH_ALEN) == 0 && + os_memcmp_const(r1->pmk_r1_name, pmk_r1_name, + WPA_PMK_NAME_LEN) == 0) { + os_memcpy(pmk_r1, r1->pmk_r1, PMK_LEN); + if (pairwise) + *pairwise = r1->pairwise; + return 0; + } + + r1 = r1->next; + } + + return -1; +} + + +static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm, + const u8 *ies, size_t ies_len, + const u8 *pmk_r0_name) +{ + struct ft_remote_r0kh *r0kh; + struct ft_r0kh_r1kh_pull_frame frame, f; + + r0kh = sm->wpa_auth->conf.r0kh_list; + while (r0kh) { + if (r0kh->id_len == sm->r0kh_id_len && + os_memcmp_const(r0kh->id, sm->r0kh_id, sm->r0kh_id_len) == + 0) + break; + r0kh = r0kh->next; + } + if (r0kh == NULL) { + wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID", + sm->r0kh_id, sm->r0kh_id_len); + return -1; + } + + wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull request to remote R0KH " + "address " MACSTR, MAC2STR(r0kh->addr)); + + os_memset(&frame, 0, sizeof(frame)); + frame.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; + frame.packet_type = FT_PACKET_R0KH_R1KH_PULL; + frame.data_length = host_to_le16(FT_R0KH_R1KH_PULL_DATA_LEN); + os_memcpy(frame.ap_address, sm->wpa_auth->addr, ETH_ALEN); + + /* aes_wrap() does not support inplace encryption, so use a temporary + * buffer for the data. */ + if (random_get_bytes(f.nonce, FT_R0KH_R1KH_PULL_NONCE_LEN)) { + wpa_printf(MSG_DEBUG, "FT: Failed to get random data for " + "nonce"); + return -1; + } + os_memcpy(sm->ft_pending_pull_nonce, f.nonce, + FT_R0KH_R1KH_PULL_NONCE_LEN); + os_memcpy(f.pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN); + os_memcpy(f.r1kh_id, sm->wpa_auth->conf.r1_key_holder, FT_R1KH_ID_LEN); + os_memcpy(f.s1kh_id, sm->addr, ETH_ALEN); + os_memset(f.pad, 0, sizeof(f.pad)); + + if (aes_wrap(r0kh->key, sizeof(r0kh->key), + (FT_R0KH_R1KH_PULL_DATA_LEN + 7) / 8, + f.nonce, frame.nonce) < 0) + return -1; + + wpabuf_free(sm->ft_pending_req_ies); + sm->ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len); + if (sm->ft_pending_req_ies == NULL) + return -1; + + wpa_ft_rrb_send(sm->wpa_auth, r0kh->addr, (u8 *) &frame, sizeof(frame)); + + return 0; +} + + +int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk, + struct wpa_ptk *ptk) +{ + u8 pmk_r0[PMK_LEN], pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 pmk_r1[PMK_LEN]; + u8 ptk_name[WPA_PMK_NAME_LEN]; + const u8 *mdid = sm->wpa_auth->conf.mobility_domain; + const u8 *r0kh = sm->wpa_auth->conf.r0_key_holder; + size_t r0kh_len = sm->wpa_auth->conf.r0_key_holder_len; + const u8 *r1kh = sm->wpa_auth->conf.r1_key_holder; + const u8 *ssid = sm->wpa_auth->conf.ssid; + size_t ssid_len = sm->wpa_auth->conf.ssid_len; + + if (sm->xxkey_len == 0) { + wpa_printf(MSG_DEBUG, "FT: XXKey not available for key " + "derivation"); + return -1; + } + + wpa_derive_pmk_r0(sm->xxkey, sm->xxkey_len, ssid, ssid_len, mdid, + r0kh, r0kh_len, sm->addr, pmk_r0, pmk_r0_name); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R0", pmk_r0, PMK_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", pmk_r0_name, WPA_PMK_NAME_LEN); + wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_name, + sm->pairwise); + + wpa_derive_pmk_r1(pmk_r0, pmk_r0_name, r1kh, sm->addr, + pmk_r1, sm->pmk_r1_name); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", pmk_r1, PMK_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", sm->pmk_r1_name, + WPA_PMK_NAME_LEN); + wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1, sm->pmk_r1_name, + sm->pairwise); + + return wpa_pmk_r1_to_ptk(pmk_r1, sm->SNonce, sm->ANonce, sm->addr, + sm->wpa_auth->addr, sm->pmk_r1_name, + ptk, ptk_name, sm->wpa_key_mgmt, sm->pairwise); +} + + +static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth, + const u8 *addr, int idx, u8 *seq) +{ + if (wpa_auth->cb.get_seqnum == NULL) + return -1; + return wpa_auth->cb.get_seqnum(wpa_auth->cb.ctx, addr, idx, seq); +} + + +static u8 * wpa_ft_gtk_subelem(struct wpa_state_machine *sm, size_t *len) +{ + u8 *subelem; + struct wpa_group *gsm = sm->group; + size_t subelem_len, pad_len; + const u8 *key; + size_t key_len; + u8 keybuf[32]; + + key_len = gsm->GTK_len; + if (key_len > sizeof(keybuf)) + return NULL; + + /* + * Pad key for AES Key Wrap if it is not multiple of 8 bytes or is less + * than 16 bytes. + */ + pad_len = key_len % 8; + if (pad_len) + pad_len = 8 - pad_len; + if (key_len + pad_len < 16) + pad_len += 8; + if (pad_len && key_len < sizeof(keybuf)) { + os_memcpy(keybuf, gsm->GTK[gsm->GN - 1], key_len); + os_memset(keybuf + key_len, 0, pad_len); + keybuf[key_len] = 0xdd; + key_len += pad_len; + key = keybuf; + } else + key = gsm->GTK[gsm->GN - 1]; + + /* + * Sub-elem ID[1] | Length[1] | Key Info[2] | Key Length[1] | RSC[8] | + * Key[5..32]. + */ + subelem_len = 13 + key_len + 8; + subelem = os_zalloc(subelem_len); + if (subelem == NULL) + return NULL; + + subelem[0] = FTIE_SUBELEM_GTK; + subelem[1] = 11 + key_len + 8; + /* Key ID in B0-B1 of Key Info */ + WPA_PUT_LE16(&subelem[2], gsm->GN & 0x03); + subelem[4] = gsm->GTK_len; + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, subelem + 5); + if (aes_wrap(sm->PTK.kek, sm->PTK.kek_len, key_len / 8, key, + subelem + 13)) { + os_free(subelem); + return NULL; + } + + *len = subelem_len; + return subelem; +} + + +#ifdef CONFIG_IEEE80211W +static u8 * wpa_ft_igtk_subelem(struct wpa_state_machine *sm, size_t *len) +{ + u8 *subelem, *pos; + struct wpa_group *gsm = sm->group; + size_t subelem_len; + + /* Sub-elem ID[1] | Length[1] | KeyID[2] | IPN[6] | Key Length[1] | + * Key[16+8] */ + subelem_len = 1 + 1 + 2 + 6 + 1 + WPA_IGTK_LEN + 8; + subelem = os_zalloc(subelem_len); + if (subelem == NULL) + return NULL; + + pos = subelem; + *pos++ = FTIE_SUBELEM_IGTK; + *pos++ = subelem_len - 2; + WPA_PUT_LE16(pos, gsm->GN_igtk); + pos += 2; + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, pos); + pos += 6; + *pos++ = WPA_IGTK_LEN; + if (aes_wrap(sm->PTK.kek, sm->PTK.kek_len, WPA_IGTK_LEN / 8, + gsm->IGTK[gsm->GN_igtk - 4], pos)) { + os_free(subelem); + return NULL; + } + + *len = subelem_len; + return subelem; +} +#endif /* CONFIG_IEEE80211W */ + + +static u8 * wpa_ft_process_rdie(struct wpa_state_machine *sm, + u8 *pos, u8 *end, u8 id, u8 descr_count, + const u8 *ies, size_t ies_len) +{ + struct ieee802_11_elems parse; + struct rsn_rdie *rdie; + + wpa_printf(MSG_DEBUG, "FT: Resource Request: id=%d descr_count=%d", + id, descr_count); + wpa_hexdump(MSG_MSGDUMP, "FT: Resource descriptor IE(s)", + ies, ies_len); + + if (end - pos < (int) sizeof(*rdie)) { + wpa_printf(MSG_ERROR, "FT: Not enough room for response RDIE"); + return pos; + } + + *pos++ = WLAN_EID_RIC_DATA; + *pos++ = sizeof(*rdie); + rdie = (struct rsn_rdie *) pos; + rdie->id = id; + rdie->descr_count = 0; + rdie->status_code = host_to_le16(WLAN_STATUS_SUCCESS); + pos += sizeof(*rdie); + + if (ieee802_11_parse_elems((u8 *) ies, ies_len, &parse, 1) == + ParseFailed) { + wpa_printf(MSG_DEBUG, "FT: Failed to parse request IEs"); + rdie->status_code = + host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; + } + + if (parse.wmm_tspec) { + struct wmm_tspec_element *tspec; + + if (parse.wmm_tspec_len + 2 < (int) sizeof(*tspec)) { + wpa_printf(MSG_DEBUG, "FT: Too short WMM TSPEC IE " + "(%d)", (int) parse.wmm_tspec_len); + rdie->status_code = + host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; + } + if (end - pos < (int) sizeof(*tspec)) { + wpa_printf(MSG_ERROR, "FT: Not enough room for " + "response TSPEC"); + rdie->status_code = + host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; + } + tspec = (struct wmm_tspec_element *) pos; + os_memcpy(tspec, parse.wmm_tspec - 2, sizeof(*tspec)); + } + +#ifdef NEED_AP_MLME + if (parse.wmm_tspec && sm->wpa_auth->conf.ap_mlme) { + int res; + + res = wmm_process_tspec((struct wmm_tspec_element *) pos); + wpa_printf(MSG_DEBUG, "FT: ADDTS processing result: %d", res); + if (res == WMM_ADDTS_STATUS_INVALID_PARAMETERS) + rdie->status_code = + host_to_le16(WLAN_STATUS_INVALID_PARAMETERS); + else if (res == WMM_ADDTS_STATUS_REFUSED) + rdie->status_code = + host_to_le16(WLAN_STATUS_REQUEST_DECLINED); + else { + /* TSPEC accepted; include updated TSPEC in response */ + rdie->descr_count = 1; + pos += sizeof(struct wmm_tspec_element); + } + return pos; + } +#endif /* NEED_AP_MLME */ + + if (parse.wmm_tspec && !sm->wpa_auth->conf.ap_mlme) { + int res; + + res = wpa_ft_add_tspec(sm->wpa_auth, sm->addr, pos, + sizeof(struct wmm_tspec_element)); + if (res >= 0) { + if (res) + rdie->status_code = host_to_le16(res); + else { + /* TSPEC accepted; include updated TSPEC in + * response */ + rdie->descr_count = 1; + pos += sizeof(struct wmm_tspec_element); + } + return pos; + } + } + + wpa_printf(MSG_DEBUG, "FT: No supported resource requested"); + rdie->status_code = host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; +} + + +static u8 * wpa_ft_process_ric(struct wpa_state_machine *sm, u8 *pos, u8 *end, + const u8 *ric, size_t ric_len) +{ + const u8 *rpos, *start; + const struct rsn_rdie *rdie; + + wpa_hexdump(MSG_MSGDUMP, "FT: RIC Request", ric, ric_len); + + rpos = ric; + while (rpos + sizeof(*rdie) < ric + ric_len) { + if (rpos[0] != WLAN_EID_RIC_DATA || rpos[1] < sizeof(*rdie) || + rpos + 2 + rpos[1] > ric + ric_len) + break; + rdie = (const struct rsn_rdie *) (rpos + 2); + rpos += 2 + rpos[1]; + start = rpos; + + while (rpos + 2 <= ric + ric_len && + rpos + 2 + rpos[1] <= ric + ric_len) { + if (rpos[0] == WLAN_EID_RIC_DATA) + break; + rpos += 2 + rpos[1]; + } + pos = wpa_ft_process_rdie(sm, pos, end, rdie->id, + rdie->descr_count, + start, rpos - start); + } + + return pos; +} + + +u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos, + size_t max_len, int auth_alg, + const u8 *req_ies, size_t req_ies_len) +{ + u8 *end, *mdie, *ftie, *rsnie = NULL, *r0kh_id, *subelem = NULL; + size_t mdie_len, ftie_len, rsnie_len = 0, r0kh_id_len, subelem_len = 0; + int res; + struct wpa_auth_config *conf; + struct rsn_ftie *_ftie; + struct wpa_ft_ies parse; + u8 *ric_start; + u8 *anonce, *snonce; + + if (sm == NULL) + return pos; + + conf = &sm->wpa_auth->conf; + + if (!wpa_key_mgmt_ft(sm->wpa_key_mgmt)) + return pos; + + end = pos + max_len; + + if (auth_alg == WLAN_AUTH_FT) { + /* + * RSN (only present if this is a Reassociation Response and + * part of a fast BSS transition) + */ + res = wpa_write_rsn_ie(conf, pos, end - pos, sm->pmk_r1_name); + if (res < 0) + return pos; + rsnie = pos; + rsnie_len = res; + pos += res; + } + + /* Mobility Domain Information */ + res = wpa_write_mdie(conf, pos, end - pos); + if (res < 0) + return pos; + mdie = pos; + mdie_len = res; + pos += res; + + /* Fast BSS Transition Information */ + if (auth_alg == WLAN_AUTH_FT) { + subelem = wpa_ft_gtk_subelem(sm, &subelem_len); + r0kh_id = sm->r0kh_id; + r0kh_id_len = sm->r0kh_id_len; + anonce = sm->ANonce; + snonce = sm->SNonce; +#ifdef CONFIG_IEEE80211W + if (sm->mgmt_frame_prot) { + u8 *igtk; + size_t igtk_len; + u8 *nbuf; + igtk = wpa_ft_igtk_subelem(sm, &igtk_len); + if (igtk == NULL) { + os_free(subelem); + return pos; + } + nbuf = os_realloc(subelem, subelem_len + igtk_len); + if (nbuf == NULL) { + os_free(subelem); + os_free(igtk); + return pos; + } + subelem = nbuf; + os_memcpy(subelem + subelem_len, igtk, igtk_len); + subelem_len += igtk_len; + os_free(igtk); + } +#endif /* CONFIG_IEEE80211W */ + } else { + r0kh_id = conf->r0_key_holder; + r0kh_id_len = conf->r0_key_holder_len; + anonce = NULL; + snonce = NULL; + } + res = wpa_write_ftie(conf, r0kh_id, r0kh_id_len, anonce, snonce, pos, + end - pos, subelem, subelem_len); + os_free(subelem); + if (res < 0) + return pos; + ftie = pos; + ftie_len = res; + pos += res; + + os_free(sm->assoc_resp_ftie); + sm->assoc_resp_ftie = os_malloc(ftie_len); + if (sm->assoc_resp_ftie) + os_memcpy(sm->assoc_resp_ftie, ftie, ftie_len); + + _ftie = (struct rsn_ftie *) (ftie + 2); + if (auth_alg == WLAN_AUTH_FT) + _ftie->mic_control[1] = 3; /* Information element count */ + + ric_start = pos; + if (wpa_ft_parse_ies(req_ies, req_ies_len, &parse) == 0 && parse.ric) { + pos = wpa_ft_process_ric(sm, pos, end, parse.ric, + parse.ric_len); + if (auth_alg == WLAN_AUTH_FT) + _ftie->mic_control[1] += + ieee802_11_ie_count(ric_start, + pos - ric_start); + } + if (ric_start == pos) + ric_start = NULL; + + if (auth_alg == WLAN_AUTH_FT && + wpa_ft_mic(sm->PTK.kck, sm->PTK.kck_len, sm->addr, + sm->wpa_auth->addr, 6, + mdie, mdie_len, ftie, ftie_len, + rsnie, rsnie_len, + ric_start, ric_start ? pos - ric_start : 0, + _ftie->mic) < 0) + wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC"); + + return pos; +} + + +static inline int wpa_auth_set_key(struct wpa_authenticator *wpa_auth, + int vlan_id, + enum wpa_alg alg, const u8 *addr, int idx, + u8 *key, size_t key_len) +{ + if (wpa_auth->cb.set_key == NULL) + return -1; + return wpa_auth->cb.set_key(wpa_auth->cb.ctx, vlan_id, alg, addr, idx, + key, key_len); +} + + +void wpa_ft_install_ptk(struct wpa_state_machine *sm) +{ + enum wpa_alg alg; + int klen; + + /* MLME-SETKEYS.request(PTK) */ + alg = wpa_cipher_to_alg(sm->pairwise); + klen = wpa_cipher_key_len(sm->pairwise); + if (!wpa_cipher_valid_pairwise(sm->pairwise)) { + wpa_printf(MSG_DEBUG, "FT: Unknown pairwise alg 0x%x - skip " + "PTK configuration", sm->pairwise); + return; + } + + /* FIX: add STA entry to kernel/driver here? The set_key will fail + * most likely without this.. At the moment, STA entry is added only + * after association has been completed. This function will be called + * again after association to get the PTK configured, but that could be + * optimized by adding the STA entry earlier. + */ + if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0, + sm->PTK.tk, klen)) + return; + + /* FIX: MLME-SetProtection.Request(TA, Tx_Rx) */ + sm->pairwise_set = TRUE; +} + + +static int wpa_ft_process_auth_req(struct wpa_state_machine *sm, + const u8 *ies, size_t ies_len, + u8 **resp_ies, size_t *resp_ies_len) +{ + struct rsn_mdie *mdie; + struct rsn_ftie *ftie; + u8 pmk_r1[PMK_LEN], pmk_r1_name[WPA_PMK_NAME_LEN]; + u8 ptk_name[WPA_PMK_NAME_LEN]; + struct wpa_auth_config *conf; + struct wpa_ft_ies parse; + size_t buflen; + int ret; + u8 *pos, *end; + int pairwise; + + *resp_ies = NULL; + *resp_ies_len = 0; + + sm->pmk_r1_name_valid = 0; + conf = &sm->wpa_auth->conf; + + wpa_hexdump(MSG_DEBUG, "FT: Received authentication frame IEs", + ies, ies_len); + + if (wpa_ft_parse_ies(ies, ies_len, &parse) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + mdie = (struct rsn_mdie *) parse.mdie; + if (mdie == NULL || parse.mdie_len < sizeof(*mdie) || + os_memcmp(mdie->mobility_domain, + sm->wpa_auth->conf.mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Invalid MDIE"); + return WLAN_STATUS_INVALID_MDIE; + } + + ftie = (struct rsn_ftie *) parse.ftie; + if (ftie == NULL || parse.ftie_len < sizeof(*ftie)) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE"); + return WLAN_STATUS_INVALID_FTIE; + } + + os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN); + + if (parse.r0kh_id == NULL) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE - no R0KH-ID"); + return WLAN_STATUS_INVALID_FTIE; + } + + wpa_hexdump(MSG_DEBUG, "FT: STA R0KH-ID", + parse.r0kh_id, parse.r0kh_id_len); + os_memcpy(sm->r0kh_id, parse.r0kh_id, parse.r0kh_id_len); + sm->r0kh_id_len = parse.r0kh_id_len; + + if (parse.rsn_pmkid == NULL) { + wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE"); + return WLAN_STATUS_INVALID_PMKID; + } + + wpa_hexdump(MSG_DEBUG, "FT: Requested PMKR0Name", + parse.rsn_pmkid, WPA_PMK_NAME_LEN); + wpa_derive_pmk_r1_name(parse.rsn_pmkid, + sm->wpa_auth->conf.r1_key_holder, sm->addr, + pmk_r1_name); + wpa_hexdump(MSG_DEBUG, "FT: Derived requested PMKR1Name", + pmk_r1_name, WPA_PMK_NAME_LEN); + + if (wpa_ft_fetch_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1_name, pmk_r1, + &pairwise) < 0) { + if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) { + wpa_printf(MSG_DEBUG, "FT: Did not have matching " + "PMK-R1 and unknown R0KH-ID"); + return WLAN_STATUS_INVALID_PMKID; + } + + return -1; /* Status pending */ + } + + wpa_hexdump_key(MSG_DEBUG, "FT: Selected PMK-R1", pmk_r1, PMK_LEN); + sm->pmk_r1_name_valid = 1; + os_memcpy(sm->pmk_r1_name, pmk_r1_name, WPA_PMK_NAME_LEN); + + if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) { + wpa_printf(MSG_DEBUG, "FT: Failed to get random data for " + "ANonce"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + wpa_hexdump(MSG_DEBUG, "FT: Received SNonce", + sm->SNonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Generated ANonce", + sm->ANonce, WPA_NONCE_LEN); + + if (wpa_pmk_r1_to_ptk(pmk_r1, sm->SNonce, sm->ANonce, sm->addr, + sm->wpa_auth->addr, pmk_r1_name, + &sm->PTK, ptk_name, sm->wpa_key_mgmt, + pairwise) < 0) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + sm->pairwise = pairwise; + sm->PTK_valid = TRUE; + wpa_ft_install_ptk(sm); + + buflen = 2 + sizeof(struct rsn_mdie) + 2 + sizeof(struct rsn_ftie) + + 2 + FT_R1KH_ID_LEN + 200; + *resp_ies = os_zalloc(buflen); + if (*resp_ies == NULL) { + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + pos = *resp_ies; + end = *resp_ies + buflen; + + ret = wpa_write_rsn_ie(conf, pos, end - pos, parse.rsn_pmkid); + if (ret < 0) { + os_free(*resp_ies); + *resp_ies = NULL; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + pos += ret; + + ret = wpa_write_mdie(conf, pos, end - pos); + if (ret < 0) { + os_free(*resp_ies); + *resp_ies = NULL; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + pos += ret; + + ret = wpa_write_ftie(conf, parse.r0kh_id, parse.r0kh_id_len, + sm->ANonce, sm->SNonce, pos, end - pos, NULL, 0); + if (ret < 0) { + os_free(*resp_ies); + *resp_ies = NULL; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + pos += ret; + + *resp_ies_len = pos - *resp_ies; + + return WLAN_STATUS_SUCCESS; +} + + +void wpa_ft_process_auth(struct wpa_state_machine *sm, const u8 *bssid, + u16 auth_transaction, const u8 *ies, size_t ies_len, + void (*cb)(void *ctx, const u8 *dst, const u8 *bssid, + u16 auth_transaction, u16 status, + const u8 *ies, size_t ies_len), + void *ctx) +{ + u16 status; + u8 *resp_ies; + size_t resp_ies_len; + int res; + + if (sm == NULL) { + wpa_printf(MSG_DEBUG, "FT: Received authentication frame, but " + "WPA SM not available"); + return; + } + + wpa_printf(MSG_DEBUG, "FT: Received authentication frame: STA=" MACSTR + " BSSID=" MACSTR " transaction=%d", + MAC2STR(sm->addr), MAC2STR(bssid), auth_transaction); + sm->ft_pending_cb = cb; + sm->ft_pending_cb_ctx = ctx; + sm->ft_pending_auth_transaction = auth_transaction; + res = wpa_ft_process_auth_req(sm, ies, ies_len, &resp_ies, + &resp_ies_len); + if (res < 0) { + wpa_printf(MSG_DEBUG, "FT: Callback postponed until response is available"); + return; + } + status = res; + + wpa_printf(MSG_DEBUG, "FT: FT authentication response: dst=" MACSTR + " auth_transaction=%d status=%d", + MAC2STR(sm->addr), auth_transaction + 1, status); + wpa_hexdump(MSG_DEBUG, "FT: Response IEs", resp_ies, resp_ies_len); + cb(ctx, sm->addr, bssid, auth_transaction + 1, status, + resp_ies, resp_ies_len); + os_free(resp_ies); +} + + +u16 wpa_ft_validate_reassoc(struct wpa_state_machine *sm, const u8 *ies, + size_t ies_len) +{ + struct wpa_ft_ies parse; + struct rsn_mdie *mdie; + struct rsn_ftie *ftie; + u8 mic[WPA_EAPOL_KEY_MIC_MAX_LEN]; + size_t mic_len = 16; + unsigned int count; + + if (sm == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + wpa_hexdump(MSG_DEBUG, "FT: Reassoc Req IEs", ies, ies_len); + + if (wpa_ft_parse_ies(ies, ies_len, &parse) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (parse.rsn == NULL) { + wpa_printf(MSG_DEBUG, "FT: No RSNIE in Reassoc Req"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (parse.rsn_pmkid == NULL) { + wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE"); + return WLAN_STATUS_INVALID_PMKID; + } + + if (os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN) + != 0) { + wpa_printf(MSG_DEBUG, "FT: PMKID in Reassoc Req did not match " + "with the PMKR1Name derived from auth request"); + return WLAN_STATUS_INVALID_PMKID; + } + + mdie = (struct rsn_mdie *) parse.mdie; + if (mdie == NULL || parse.mdie_len < sizeof(*mdie) || + os_memcmp(mdie->mobility_domain, + sm->wpa_auth->conf.mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Invalid MDIE"); + return WLAN_STATUS_INVALID_MDIE; + } + + ftie = (struct rsn_ftie *) parse.ftie; + if (ftie == NULL || parse.ftie_len < sizeof(*ftie)) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE"); + return WLAN_STATUS_INVALID_FTIE; + } + + if (os_memcmp(ftie->snonce, sm->SNonce, WPA_NONCE_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: SNonce mismatch in FTIE"); + wpa_hexdump(MSG_DEBUG, "FT: Received SNonce", + ftie->snonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce", + sm->SNonce, WPA_NONCE_LEN); + return -1; + } + + if (os_memcmp(ftie->anonce, sm->ANonce, WPA_NONCE_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: ANonce mismatch in FTIE"); + wpa_hexdump(MSG_DEBUG, "FT: Received ANonce", + ftie->anonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce", + sm->ANonce, WPA_NONCE_LEN); + return -1; + } + + + if (parse.r0kh_id == NULL) { + wpa_printf(MSG_DEBUG, "FT: No R0KH-ID subelem in FTIE"); + return -1; + } + + if (parse.r0kh_id_len != sm->r0kh_id_len || + os_memcmp_const(parse.r0kh_id, sm->r0kh_id, parse.r0kh_id_len) != 0) + { + wpa_printf(MSG_DEBUG, "FT: R0KH-ID in FTIE did not match with " + "the current R0KH-ID"); + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID in FTIE", + parse.r0kh_id, parse.r0kh_id_len); + wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID", + sm->r0kh_id, sm->r0kh_id_len); + return -1; + } + + if (parse.r1kh_id == NULL) { + wpa_printf(MSG_DEBUG, "FT: No R1KH-ID subelem in FTIE"); + return -1; + } + + if (os_memcmp_const(parse.r1kh_id, sm->wpa_auth->conf.r1_key_holder, + FT_R1KH_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Unknown R1KH-ID used in " + "ReassocReq"); + wpa_hexdump(MSG_DEBUG, "FT: R1KH-ID in FTIE", + parse.r1kh_id, FT_R1KH_ID_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected R1KH-ID", + sm->wpa_auth->conf.r1_key_holder, FT_R1KH_ID_LEN); + return -1; + } + + if (parse.rsn_pmkid == NULL || + os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN)) + { + wpa_printf(MSG_DEBUG, "FT: No matching PMKR1Name (PMKID) in " + "RSNIE (pmkid=%d)", !!parse.rsn_pmkid); + return -1; + } + + count = 3; + if (parse.ric) + count += ieee802_11_ie_count(parse.ric, parse.ric_len); + if (ftie->mic_control[1] != count) { + wpa_printf(MSG_DEBUG, "FT: Unexpected IE count in MIC " + "Control: received %u expected %u", + ftie->mic_control[1], count); + return -1; + } + + if (wpa_ft_mic(sm->PTK.kck, sm->PTK.kck_len, sm->addr, + sm->wpa_auth->addr, 5, + parse.mdie - 2, parse.mdie_len + 2, + parse.ftie - 2, parse.ftie_len + 2, + parse.rsn - 2, parse.rsn_len + 2, + parse.ric, parse.ric_len, + mic) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (os_memcmp_const(mic, ftie->mic, mic_len) != 0) { + wpa_printf(MSG_DEBUG, "FT: Invalid MIC in FTIE"); + wpa_printf(MSG_DEBUG, "FT: addr=" MACSTR " auth_addr=" MACSTR, + MAC2STR(sm->addr), MAC2STR(sm->wpa_auth->addr)); + wpa_hexdump(MSG_MSGDUMP, "FT: Received MIC", + ftie->mic, mic_len); + wpa_hexdump(MSG_MSGDUMP, "FT: Calculated MIC", mic, mic_len); + wpa_hexdump(MSG_MSGDUMP, "FT: MDIE", + parse.mdie - 2, parse.mdie_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: FTIE", + parse.ftie - 2, parse.ftie_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: RSN", + parse.rsn - 2, parse.rsn_len + 2); + return WLAN_STATUS_INVALID_FTIE; + } + + return WLAN_STATUS_SUCCESS; +} + + +int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len) +{ + const u8 *sta_addr, *target_ap; + const u8 *ies; + size_t ies_len; + u8 action; + struct ft_rrb_frame *frame; + + if (sm == NULL) + return -1; + + /* + * data: Category[1] Action[1] STA_Address[6] Target_AP_Address[6] + * FT Request action frame body[variable] + */ + + if (len < 14) { + wpa_printf(MSG_DEBUG, "FT: Too short FT Action frame " + "(len=%lu)", (unsigned long) len); + return -1; + } + + action = data[1]; + sta_addr = data + 2; + target_ap = data + 8; + ies = data + 14; + ies_len = len - 14; + + wpa_printf(MSG_DEBUG, "FT: Received FT Action frame (STA=" MACSTR + " Target AP=" MACSTR " Action=%d)", + MAC2STR(sta_addr), MAC2STR(target_ap), action); + + if (os_memcmp(sta_addr, sm->addr, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Mismatch in FT Action STA address: " + "STA=" MACSTR " STA-Address=" MACSTR, + MAC2STR(sm->addr), MAC2STR(sta_addr)); + return -1; + } + + /* + * Do some sanity checking on the target AP address (not own and not + * broadcast. This could be extended to filter based on a list of known + * APs in the MD (if such a list were configured). + */ + if ((target_ap[0] & 0x01) || + os_memcmp(target_ap, sm->wpa_auth->addr, ETH_ALEN) == 0) { + wpa_printf(MSG_DEBUG, "FT: Invalid Target AP in FT Action " + "frame"); + return -1; + } + + wpa_hexdump(MSG_MSGDUMP, "FT: Action frame body", ies, ies_len); + + /* RRB - Forward action frame to the target AP */ + frame = os_malloc(sizeof(*frame) + len); + if (frame == NULL) + return -1; + frame->frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; + frame->packet_type = FT_PACKET_REQUEST; + frame->action_length = host_to_le16(len); + os_memcpy(frame->ap_address, sm->wpa_auth->addr, ETH_ALEN); + os_memcpy(frame + 1, data, len); + + wpa_ft_rrb_send(sm->wpa_auth, target_ap, (u8 *) frame, + sizeof(*frame) + len); + os_free(frame); + + return 0; +} + + +static void wpa_ft_rrb_rx_request_cb(void *ctx, const u8 *dst, const u8 *bssid, + u16 auth_transaction, u16 resp, + const u8 *ies, size_t ies_len) +{ + struct wpa_state_machine *sm = ctx; + wpa_printf(MSG_DEBUG, "FT: Over-the-DS RX request cb for " MACSTR, + MAC2STR(sm->addr)); + wpa_ft_send_rrb_auth_resp(sm, sm->ft_pending_current_ap, sm->addr, + WLAN_STATUS_SUCCESS, ies, ies_len); +} + + +static int wpa_ft_rrb_rx_request(struct wpa_authenticator *wpa_auth, + const u8 *current_ap, const u8 *sta_addr, + const u8 *body, size_t len) +{ + struct wpa_state_machine *sm; + u16 status; + u8 *resp_ies; + size_t resp_ies_len; + int res; + + sm = wpa_ft_add_sta(wpa_auth, sta_addr); + if (sm == NULL) { + wpa_printf(MSG_DEBUG, "FT: Failed to add new STA based on " + "RRB Request"); + return -1; + } + + wpa_hexdump(MSG_MSGDUMP, "FT: RRB Request Frame body", body, len); + + sm->ft_pending_cb = wpa_ft_rrb_rx_request_cb; + sm->ft_pending_cb_ctx = sm; + os_memcpy(sm->ft_pending_current_ap, current_ap, ETH_ALEN); + res = wpa_ft_process_auth_req(sm, body, len, &resp_ies, + &resp_ies_len); + if (res < 0) { + wpa_printf(MSG_DEBUG, "FT: No immediate response available - wait for pull response"); + return 0; + } + status = res; + + res = wpa_ft_send_rrb_auth_resp(sm, current_ap, sta_addr, status, + resp_ies, resp_ies_len); + os_free(resp_ies); + return res; +} + + +static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm, + const u8 *current_ap, const u8 *sta_addr, + u16 status, const u8 *resp_ies, + size_t resp_ies_len) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + size_t rlen; + struct ft_rrb_frame *frame; + u8 *pos; + + wpa_printf(MSG_DEBUG, "FT: RRB authentication response: STA=" MACSTR + " CurrentAP=" MACSTR " status=%d", + MAC2STR(sm->addr), MAC2STR(current_ap), status); + wpa_hexdump(MSG_DEBUG, "FT: Response IEs", resp_ies, resp_ies_len); + + /* RRB - Forward action frame response to the Current AP */ + + /* + * data: Category[1] Action[1] STA_Address[6] Target_AP_Address[6] + * Status_Code[2] FT Request action frame body[variable] + */ + rlen = 2 + 2 * ETH_ALEN + 2 + resp_ies_len; + + frame = os_malloc(sizeof(*frame) + rlen); + if (frame == NULL) + return -1; + frame->frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; + frame->packet_type = FT_PACKET_RESPONSE; + frame->action_length = host_to_le16(rlen); + os_memcpy(frame->ap_address, wpa_auth->addr, ETH_ALEN); + pos = (u8 *) (frame + 1); + *pos++ = WLAN_ACTION_FT; + *pos++ = 2; /* Action: Response */ + os_memcpy(pos, sta_addr, ETH_ALEN); + pos += ETH_ALEN; + os_memcpy(pos, wpa_auth->addr, ETH_ALEN); + pos += ETH_ALEN; + WPA_PUT_LE16(pos, status); + pos += 2; + if (resp_ies) + os_memcpy(pos, resp_ies, resp_ies_len); + + wpa_ft_rrb_send(wpa_auth, current_ap, (u8 *) frame, + sizeof(*frame) + rlen); + os_free(frame); + + return 0; +} + + +static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *data, size_t data_len) +{ + struct ft_r0kh_r1kh_pull_frame f; + const u8 *crypt; + u8 *plain; + struct ft_remote_r1kh *r1kh; + struct ft_r0kh_r1kh_resp_frame resp, r; + u8 pmk_r0[PMK_LEN]; + int pairwise; + + wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull"); + + if (data_len < sizeof(f)) + return -1; + + r1kh = wpa_auth->conf.r1kh_list; + while (r1kh) { + if (os_memcmp(r1kh->addr, src_addr, ETH_ALEN) == 0) + break; + r1kh = r1kh->next; + } + if (r1kh == NULL) { + wpa_printf(MSG_DEBUG, "FT: No matching R1KH address found for " + "PMK-R1 pull source address " MACSTR, + MAC2STR(src_addr)); + return -1; + } + + crypt = data + offsetof(struct ft_r0kh_r1kh_pull_frame, nonce); + os_memset(&f, 0, sizeof(f)); + plain = ((u8 *) &f) + offsetof(struct ft_r0kh_r1kh_pull_frame, nonce); + /* aes_unwrap() does not support inplace decryption, so use a temporary + * buffer for the data. */ + if (aes_unwrap(r1kh->key, sizeof(r1kh->key), + (FT_R0KH_R1KH_PULL_DATA_LEN + 7) / 8, + crypt, plain) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 pull " + "request from " MACSTR, MAC2STR(src_addr)); + return -1; + } + + wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce", + f.nonce, sizeof(f.nonce)); + wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR0Name", + f.pmk_r0_name, WPA_PMK_NAME_LEN); + wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR " S1KH-ID=" + MACSTR, MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id)); + + os_memset(&resp, 0, sizeof(resp)); + resp.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; + resp.packet_type = FT_PACKET_R0KH_R1KH_RESP; + resp.data_length = host_to_le16(FT_R0KH_R1KH_RESP_DATA_LEN); + os_memcpy(resp.ap_address, wpa_auth->addr, ETH_ALEN); + + /* aes_wrap() does not support inplace encryption, so use a temporary + * buffer for the data. */ + os_memcpy(r.nonce, f.nonce, sizeof(f.nonce)); + os_memcpy(r.r1kh_id, f.r1kh_id, FT_R1KH_ID_LEN); + os_memcpy(r.s1kh_id, f.s1kh_id, ETH_ALEN); + if (wpa_ft_fetch_pmk_r0(wpa_auth, f.s1kh_id, f.pmk_r0_name, pmk_r0, + &pairwise) < 0) { + wpa_printf(MSG_DEBUG, "FT: No matching PMKR0Name found for " + "PMK-R1 pull"); + return -1; + } + + wpa_derive_pmk_r1(pmk_r0, f.pmk_r0_name, f.r1kh_id, f.s1kh_id, + r.pmk_r1, r.pmk_r1_name); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", r.pmk_r1, PMK_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", r.pmk_r1_name, + WPA_PMK_NAME_LEN); + r.pairwise = host_to_le16(pairwise); + os_memset(r.pad, 0, sizeof(r.pad)); + + if (aes_wrap(r1kh->key, sizeof(r1kh->key), + (FT_R0KH_R1KH_RESP_DATA_LEN + 7) / 8, + r.nonce, resp.nonce) < 0) { + os_memset(pmk_r0, 0, PMK_LEN); + return -1; + } + + os_memset(pmk_r0, 0, PMK_LEN); + + wpa_ft_rrb_send(wpa_auth, src_addr, (u8 *) &resp, sizeof(resp)); + + return 0; +} + + +static void ft_pull_resp_cb_finish(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_state_machine *sm = eloop_ctx; + int res; + u8 *resp_ies; + size_t resp_ies_len; + u16 status; + + res = wpa_ft_process_auth_req(sm, wpabuf_head(sm->ft_pending_req_ies), + wpabuf_len(sm->ft_pending_req_ies), + &resp_ies, &resp_ies_len); + wpabuf_free(sm->ft_pending_req_ies); + sm->ft_pending_req_ies = NULL; + if (res < 0) + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + status = res; + wpa_printf(MSG_DEBUG, "FT: Postponed auth callback result for " MACSTR + " - status %u", MAC2STR(sm->addr), status); + + sm->ft_pending_cb(sm->ft_pending_cb_ctx, sm->addr, sm->wpa_auth->addr, + sm->ft_pending_auth_transaction + 1, status, + resp_ies, resp_ies_len); + os_free(resp_ies); +} + + +static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx) +{ + struct ft_r0kh_r1kh_resp_frame *frame = ctx; + + if (os_memcmp(frame->s1kh_id, sm->addr, ETH_ALEN) != 0) + return 0; + if (os_memcmp(frame->nonce, sm->ft_pending_pull_nonce, + FT_R0KH_R1KH_PULL_NONCE_LEN) != 0) + return 0; + if (sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL) + return 0; + + wpa_printf(MSG_DEBUG, "FT: Response to a pending pull request for " + MACSTR " - process from timeout", MAC2STR(sm->addr)); + eloop_register_timeout(0, 0, ft_pull_resp_cb_finish, sm, NULL); + return 1; +} + + +static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *data, size_t data_len) +{ + struct ft_r0kh_r1kh_resp_frame f; + const u8 *crypt; + u8 *plain; + struct ft_remote_r0kh *r0kh; + int pairwise, res; + + wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull response"); + + if (data_len < sizeof(f)) + return -1; + + r0kh = wpa_auth->conf.r0kh_list; + while (r0kh) { + if (os_memcmp(r0kh->addr, src_addr, ETH_ALEN) == 0) + break; + r0kh = r0kh->next; + } + if (r0kh == NULL) { + wpa_printf(MSG_DEBUG, "FT: No matching R0KH address found for " + "PMK-R0 pull response source address " MACSTR, + MAC2STR(src_addr)); + return -1; + } + + crypt = data + offsetof(struct ft_r0kh_r1kh_resp_frame, nonce); + os_memset(&f, 0, sizeof(f)); + plain = ((u8 *) &f) + offsetof(struct ft_r0kh_r1kh_resp_frame, nonce); + /* aes_unwrap() does not support inplace decryption, so use a temporary + * buffer for the data. */ + if (aes_unwrap(r0kh->key, sizeof(r0kh->key), + (FT_R0KH_R1KH_RESP_DATA_LEN + 7) / 8, + crypt, plain) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 pull " + "response from " MACSTR, MAC2STR(src_addr)); + return -1; + } + + if (os_memcmp_const(f.r1kh_id, wpa_auth->conf.r1_key_holder, + FT_R1KH_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull response did not use a " + "matching R1KH-ID"); + return -1; + } + + pairwise = le_to_host16(f.pairwise); + wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce", + f.nonce, sizeof(f.nonce)); + wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR " S1KH-ID=" + MACSTR " pairwise=0x%x", + MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id), pairwise); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1 pull - PMK-R1", + f.pmk_r1, PMK_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR1Name", + f.pmk_r1_name, WPA_PMK_NAME_LEN); + + res = wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name, + pairwise); + wpa_printf(MSG_DEBUG, "FT: Look for pending pull request"); + wpa_auth_for_each_sta(wpa_auth, ft_pull_resp_cb, &f); + os_memset(f.pmk_r1, 0, PMK_LEN); + + return res ? 0 : -1; +} + + +static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *data, size_t data_len) +{ + struct ft_r0kh_r1kh_push_frame f; + const u8 *crypt; + u8 *plain; + struct ft_remote_r0kh *r0kh; + struct os_time now; + os_time_t tsend; + int pairwise; + + wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 push"); + + if (data_len < sizeof(f)) + return -1; + + r0kh = wpa_auth->conf.r0kh_list; + while (r0kh) { + if (os_memcmp(r0kh->addr, src_addr, ETH_ALEN) == 0) + break; + r0kh = r0kh->next; + } + if (r0kh == NULL) { + wpa_printf(MSG_DEBUG, "FT: No matching R0KH address found for " + "PMK-R0 push source address " MACSTR, + MAC2STR(src_addr)); + return -1; + } + + crypt = data + offsetof(struct ft_r0kh_r1kh_push_frame, timestamp); + os_memset(&f, 0, sizeof(f)); + plain = ((u8 *) &f) + offsetof(struct ft_r0kh_r1kh_push_frame, + timestamp); + /* aes_unwrap() does not support inplace decryption, so use a temporary + * buffer for the data. */ + if (aes_unwrap(r0kh->key, sizeof(r0kh->key), + (FT_R0KH_R1KH_PUSH_DATA_LEN + 7) / 8, + crypt, plain) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to decrypt PMK-R1 push from " + MACSTR, MAC2STR(src_addr)); + return -1; + } + + os_get_time(&now); + tsend = WPA_GET_LE32(f.timestamp); + if ((now.sec > tsend && now.sec - tsend > 60) || + (now.sec < tsend && tsend - now.sec > 60)) { + wpa_printf(MSG_DEBUG, "FT: PMK-R1 push did not have a valid " + "timestamp: sender time %d own time %d\n", + (int) tsend, (int) now.sec); + return -1; + } + + if (os_memcmp_const(f.r1kh_id, wpa_auth->conf.r1_key_holder, + FT_R1KH_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: PMK-R1 push did not use a matching " + "R1KH-ID (received " MACSTR " own " MACSTR ")", + MAC2STR(f.r1kh_id), + MAC2STR(wpa_auth->conf.r1_key_holder)); + return -1; + } + + pairwise = le_to_host16(f.pairwise); + wpa_printf(MSG_DEBUG, "FT: PMK-R1 push - R1KH-ID=" MACSTR " S1KH-ID=" + MACSTR " pairwise=0x%x", + MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id), pairwise); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1 push - PMK-R1", + f.pmk_r1, PMK_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 push - PMKR1Name", + f.pmk_r1_name, WPA_PMK_NAME_LEN); + + wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name, + pairwise); + os_memset(f.pmk_r1, 0, PMK_LEN); + + return 0; +} + + +int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, + const u8 *data, size_t data_len) +{ + struct ft_rrb_frame *frame; + u16 alen; + const u8 *pos, *end, *start; + u8 action; + const u8 *sta_addr, *target_ap_addr; + + wpa_printf(MSG_DEBUG, "FT: RRB received frame from remote AP " MACSTR, + MAC2STR(src_addr)); + + if (data_len < sizeof(*frame)) { + wpa_printf(MSG_DEBUG, "FT: Too short RRB frame (data_len=%lu)", + (unsigned long) data_len); + return -1; + } + + pos = data; + frame = (struct ft_rrb_frame *) pos; + pos += sizeof(*frame); + + alen = le_to_host16(frame->action_length); + wpa_printf(MSG_DEBUG, "FT: RRB frame - frame_type=%d packet_type=%d " + "action_length=%d ap_address=" MACSTR, + frame->frame_type, frame->packet_type, alen, + MAC2STR(frame->ap_address)); + + if (frame->frame_type != RSN_REMOTE_FRAME_TYPE_FT_RRB) { + /* Discard frame per IEEE Std 802.11r-2008, 11A.10.3 */ + wpa_printf(MSG_DEBUG, "FT: RRB discarded frame with " + "unrecognized type %d", frame->frame_type); + return -1; + } + + if (alen > data_len - sizeof(*frame)) { + wpa_printf(MSG_DEBUG, "FT: RRB frame too short for action " + "frame"); + return -1; + } + + if (frame->packet_type == FT_PACKET_R0KH_R1KH_PULL) + return wpa_ft_rrb_rx_pull(wpa_auth, src_addr, data, data_len); + if (frame->packet_type == FT_PACKET_R0KH_R1KH_RESP) + return wpa_ft_rrb_rx_resp(wpa_auth, src_addr, data, data_len); + if (frame->packet_type == FT_PACKET_R0KH_R1KH_PUSH) + return wpa_ft_rrb_rx_push(wpa_auth, src_addr, data, data_len); + + wpa_hexdump(MSG_MSGDUMP, "FT: RRB - FT Action frame", pos, alen); + + if (alen < 1 + 1 + 2 * ETH_ALEN) { + wpa_printf(MSG_DEBUG, "FT: Too short RRB frame (not enough " + "room for Action Frame body); alen=%lu", + (unsigned long) alen); + return -1; + } + start = pos; + end = pos + alen; + + if (*pos != WLAN_ACTION_FT) { + wpa_printf(MSG_DEBUG, "FT: Unexpected Action frame category " + "%d", *pos); + return -1; + } + + pos++; + action = *pos++; + sta_addr = pos; + pos += ETH_ALEN; + target_ap_addr = pos; + pos += ETH_ALEN; + wpa_printf(MSG_DEBUG, "FT: RRB Action Frame: action=%d sta_addr=" + MACSTR " target_ap_addr=" MACSTR, + action, MAC2STR(sta_addr), MAC2STR(target_ap_addr)); + + if (frame->packet_type == FT_PACKET_REQUEST) { + wpa_printf(MSG_DEBUG, "FT: FT Packet Type - Request"); + + if (action != 1) { + wpa_printf(MSG_DEBUG, "FT: Unexpected Action %d in " + "RRB Request", action); + return -1; + } + + if (os_memcmp(target_ap_addr, wpa_auth->addr, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Target AP address in the " + "RRB Request does not match with own " + "address"); + return -1; + } + + if (wpa_ft_rrb_rx_request(wpa_auth, frame->ap_address, + sta_addr, pos, end - pos) < 0) + return -1; + } else if (frame->packet_type == FT_PACKET_RESPONSE) { + u16 status_code; + + if (end - pos < 2) { + wpa_printf(MSG_DEBUG, "FT: Not enough room for status " + "code in RRB Response"); + return -1; + } + status_code = WPA_GET_LE16(pos); + pos += 2; + + wpa_printf(MSG_DEBUG, "FT: FT Packet Type - Response " + "(status_code=%d)", status_code); + + if (wpa_ft_action_send(wpa_auth, sta_addr, start, alen) < 0) + return -1; + } else { + wpa_printf(MSG_DEBUG, "FT: RRB discarded frame with unknown " + "packet_type %d", frame->packet_type); + return -1; + } + + if (end > pos) { + wpa_hexdump(MSG_DEBUG, "FT: Ignore extra data in end", + pos, end - pos); + } + + return 0; +} + + +static void wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth, + struct wpa_ft_pmk_r0_sa *pmk_r0, + struct ft_remote_r1kh *r1kh, + const u8 *s1kh_id, int pairwise) +{ + struct ft_r0kh_r1kh_push_frame frame, f; + struct os_time now; + const u8 *plain; + u8 *crypt; + + os_memset(&frame, 0, sizeof(frame)); + frame.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; + frame.packet_type = FT_PACKET_R0KH_R1KH_PUSH; + frame.data_length = host_to_le16(FT_R0KH_R1KH_PUSH_DATA_LEN); + os_memcpy(frame.ap_address, wpa_auth->addr, ETH_ALEN); + + /* aes_wrap() does not support inplace encryption, so use a temporary + * buffer for the data. */ + os_memcpy(f.r1kh_id, r1kh->id, FT_R1KH_ID_LEN); + os_memcpy(f.s1kh_id, s1kh_id, ETH_ALEN); + os_memcpy(f.pmk_r0_name, pmk_r0->pmk_r0_name, WPA_PMK_NAME_LEN); + wpa_derive_pmk_r1(pmk_r0->pmk_r0, pmk_r0->pmk_r0_name, r1kh->id, + s1kh_id, f.pmk_r1, f.pmk_r1_name); + wpa_printf(MSG_DEBUG, "FT: R1KH-ID " MACSTR, MAC2STR(r1kh->id)); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", f.pmk_r1, PMK_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", f.pmk_r1_name, + WPA_PMK_NAME_LEN); + os_get_time(&now); + WPA_PUT_LE32(f.timestamp, now.sec); + f.pairwise = host_to_le16(pairwise); + os_memset(f.pad, 0, sizeof(f.pad)); + plain = ((const u8 *) &f) + offsetof(struct ft_r0kh_r1kh_push_frame, + timestamp); + crypt = ((u8 *) &frame) + offsetof(struct ft_r0kh_r1kh_push_frame, + timestamp); + if (aes_wrap(r1kh->key, sizeof(r1kh->key), + (FT_R0KH_R1KH_PUSH_DATA_LEN + 7) / 8, + plain, crypt) < 0) + return; + + wpa_ft_rrb_send(wpa_auth, r1kh->addr, (u8 *) &frame, sizeof(frame)); +} + + +void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr) +{ + struct wpa_ft_pmk_r0_sa *r0; + struct ft_remote_r1kh *r1kh; + + if (!wpa_auth->conf.pmk_r1_push) + return; + + r0 = wpa_auth->ft_pmk_cache->pmk_r0; + while (r0) { + if (os_memcmp(r0->spa, addr, ETH_ALEN) == 0) + break; + r0 = r0->next; + } + + if (r0 == NULL || r0->pmk_r1_pushed) + return; + r0->pmk_r1_pushed = 1; + + wpa_printf(MSG_DEBUG, "FT: Deriving and pushing PMK-R1 keys to R1KHs " + "for STA " MACSTR, MAC2STR(addr)); + + r1kh = wpa_auth->conf.r1kh_list; + while (r1kh) { + wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr, r0->pairwise); + r1kh = r1kh->next; + } +} + +#endif /* CONFIG_IEEE80211R */ diff --git a/freebsd/contrib/wpa/src/ap/wpa_auth_i.h b/freebsd/contrib/wpa/src/ap/wpa_auth_i.h new file mode 100644 index 00000000..57b098f2 --- /dev/null +++ b/freebsd/contrib/wpa/src/ap/wpa_auth_i.h @@ -0,0 +1,259 @@ +/* + * hostapd - IEEE 802.11i-2004 / WPA Authenticator: Internal definitions + * Copyright (c) 2004-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_I_H +#define WPA_AUTH_I_H + +/* max(dot11RSNAConfigGroupUpdateCount,dot11RSNAConfigPairwiseUpdateCount) */ +#define RSNA_MAX_EAPOL_RETRIES 4 + +struct wpa_group; + +struct wpa_stsl_negotiation { + struct wpa_stsl_negotiation *next; + u8 initiator[ETH_ALEN]; + u8 peer[ETH_ALEN]; +}; + + +struct wpa_state_machine { + struct wpa_authenticator *wpa_auth; + struct wpa_group *group; + + u8 addr[ETH_ALEN]; + u8 p2p_dev_addr[ETH_ALEN]; + + enum { + WPA_PTK_INITIALIZE, WPA_PTK_DISCONNECT, WPA_PTK_DISCONNECTED, + WPA_PTK_AUTHENTICATION, WPA_PTK_AUTHENTICATION2, + WPA_PTK_INITPMK, WPA_PTK_INITPSK, WPA_PTK_PTKSTART, + WPA_PTK_PTKCALCNEGOTIATING, WPA_PTK_PTKCALCNEGOTIATING2, + WPA_PTK_PTKINITNEGOTIATING, WPA_PTK_PTKINITDONE + } wpa_ptk_state; + + enum { + WPA_PTK_GROUP_IDLE = 0, + WPA_PTK_GROUP_REKEYNEGOTIATING, + WPA_PTK_GROUP_REKEYESTABLISHED, + WPA_PTK_GROUP_KEYERROR + } wpa_ptk_group_state; + + Boolean Init; + Boolean DeauthenticationRequest; + Boolean AuthenticationRequest; + Boolean ReAuthenticationRequest; + Boolean Disconnect; + int TimeoutCtr; + int GTimeoutCtr; + Boolean TimeoutEvt; + Boolean EAPOLKeyReceived; + Boolean EAPOLKeyPairwise; + Boolean EAPOLKeyRequest; + Boolean MICVerified; + Boolean GUpdateStationKeys; + u8 ANonce[WPA_NONCE_LEN]; + u8 SNonce[WPA_NONCE_LEN]; + u8 alt_SNonce[WPA_NONCE_LEN]; + u8 alt_replay_counter[WPA_REPLAY_COUNTER_LEN]; + u8 PMK[PMK_LEN]; + struct wpa_ptk PTK; + Boolean PTK_valid; + Boolean pairwise_set; + int keycount; + Boolean Pair; + struct wpa_key_replay_counter { + u8 counter[WPA_REPLAY_COUNTER_LEN]; + Boolean valid; + } key_replay[RSNA_MAX_EAPOL_RETRIES], + prev_key_replay[RSNA_MAX_EAPOL_RETRIES]; + Boolean PInitAKeys; /* WPA only, not in IEEE 802.11i */ + Boolean PTKRequest; /* not in IEEE 802.11i state machine */ + Boolean has_GTK; + Boolean PtkGroupInit; /* init request for PTK Group state machine */ + + u8 *last_rx_eapol_key; /* starting from IEEE 802.1X header */ + size_t last_rx_eapol_key_len; + + unsigned int changed:1; + unsigned int in_step_loop:1; + unsigned int pending_deinit:1; + unsigned int started:1; + unsigned int mgmt_frame_prot:1; + unsigned int rx_eapol_key_secure:1; + unsigned int update_snonce:1; + unsigned int alt_snonce_valid:1; +#ifdef CONFIG_IEEE80211R + unsigned int ft_completed:1; + unsigned int pmk_r1_name_valid:1; +#endif /* CONFIG_IEEE80211R */ + unsigned int is_wnmsleep:1; + + u8 req_replay_counter[WPA_REPLAY_COUNTER_LEN]; + int req_replay_counter_used; + + u8 *wpa_ie; + size_t wpa_ie_len; + + enum { + WPA_VERSION_NO_WPA = 0 /* WPA not used */, + WPA_VERSION_WPA = 1 /* WPA / IEEE 802.11i/D3.0 */, + WPA_VERSION_WPA2 = 2 /* WPA2 / IEEE 802.11i */ + } wpa; + int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */ + int wpa_key_mgmt; /* the selected WPA_KEY_MGMT_* */ + struct rsn_pmksa_cache_entry *pmksa; + + u32 dot11RSNAStatsTKIPLocalMICFailures; + u32 dot11RSNAStatsTKIPRemoteMICFailures; + +#ifdef CONFIG_IEEE80211R + u8 xxkey[PMK_LEN]; /* PSK or the second 256 bits of MSK */ + size_t xxkey_len; + u8 pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name derived from FT Auth + * Request */ + u8 r0kh_id[FT_R0KH_ID_MAX_LEN]; /* R0KH-ID from FT Auth Request */ + size_t r0kh_id_len; + u8 sup_pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name from EAPOL-Key + * message 2/4 */ + u8 *assoc_resp_ftie; + + void (*ft_pending_cb)(void *ctx, const u8 *dst, const u8 *bssid, + u16 auth_transaction, u16 status, + const u8 *ies, size_t ies_len); + void *ft_pending_cb_ctx; + struct wpabuf *ft_pending_req_ies; + u8 ft_pending_pull_nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; + u8 ft_pending_auth_transaction; + u8 ft_pending_current_ap[ETH_ALEN]; +#endif /* CONFIG_IEEE80211R */ + + int pending_1_of_4_timeout; + +#ifdef CONFIG_P2P + u8 ip_addr[4]; +#endif /* CONFIG_P2P */ +}; + + +/* per group key state machine data */ +struct wpa_group { + struct wpa_group *next; + int vlan_id; + + Boolean GInit; + int GKeyDoneStations; + Boolean GTKReKey; + int GTK_len; + int GN, GM; + Boolean GTKAuthenticator; + u8 Counter[WPA_NONCE_LEN]; + + enum { + WPA_GROUP_GTK_INIT = 0, + WPA_GROUP_SETKEYS, WPA_GROUP_SETKEYSDONE, + WPA_GROUP_FATAL_FAILURE + } wpa_group_state; + + u8 GMK[WPA_GMK_LEN]; + u8 GTK[2][WPA_GTK_MAX_LEN]; + u8 GNonce[WPA_NONCE_LEN]; + Boolean changed; + Boolean first_sta_seen; + Boolean reject_4way_hs_for_entropy; +#ifdef CONFIG_IEEE80211W + u8 IGTK[2][WPA_IGTK_MAX_LEN]; + int GN_igtk, GM_igtk; +#endif /* CONFIG_IEEE80211W */ + /* Number of references except those in struct wpa_group->next */ + unsigned int references; +}; + + +struct wpa_ft_pmk_cache; + +/* per authenticator data */ +struct wpa_authenticator { + struct wpa_group *group; + + unsigned int dot11RSNAStatsTKIPRemoteMICFailures; + u32 dot11RSNAAuthenticationSuiteSelected; + u32 dot11RSNAPairwiseCipherSelected; + u32 dot11RSNAGroupCipherSelected; + u8 dot11RSNAPMKIDUsed[PMKID_LEN]; + u32 dot11RSNAAuthenticationSuiteRequested; /* FIX: update */ + u32 dot11RSNAPairwiseCipherRequested; /* FIX: update */ + u32 dot11RSNAGroupCipherRequested; /* FIX: update */ + unsigned int dot11RSNATKIPCounterMeasuresInvoked; + unsigned int dot11RSNA4WayHandshakeFailures; + + struct wpa_stsl_negotiation *stsl_negotiations; + + struct wpa_auth_config conf; + struct wpa_auth_callbacks cb; + + u8 *wpa_ie; + size_t wpa_ie_len; + + u8 addr[ETH_ALEN]; + + struct rsn_pmksa_cache *pmksa; + struct wpa_ft_pmk_cache *ft_pmk_cache; + +#ifdef CONFIG_P2P + struct bitfield *ip_pool; +#endif /* CONFIG_P2P */ +}; + + +int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, + const u8 *pmkid); +void wpa_auth_logger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *txt); +void wpa_auth_vlogger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *fmt, ...); +void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int key_info, + const u8 *key_rsc, const u8 *nonce, + const u8 *kde, size_t kde_len, + int keyidx, int encr, int force_version); +int wpa_auth_for_each_sta(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_state_machine *sm, void *ctx), + void *cb_ctx); +int wpa_auth_for_each_auth(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_authenticator *a, void *ctx), + void *cb_ctx); + +#ifdef CONFIG_PEERKEY +int wpa_stsl_remove(struct wpa_authenticator *wpa_auth, + struct wpa_stsl_negotiation *neg); +void wpa_smk_error(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *key_data, size_t key_data_len); +void wpa_smk_m1(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, struct wpa_eapol_key *key, + const u8 *key_data, size_t key_data_len); +void wpa_smk_m3(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, struct wpa_eapol_key *key, + const u8 *key_data, size_t key_data_len); +#endif /* CONFIG_PEERKEY */ + +#ifdef CONFIG_IEEE80211R +int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len); +int wpa_write_ftie(struct wpa_auth_config *conf, const u8 *r0kh_id, + size_t r0kh_id_len, + const u8 *anonce, const u8 *snonce, + u8 *buf, size_t len, const u8 *subelem, + size_t subelem_len); +int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk, + struct wpa_ptk *ptk); +struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void); +void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache); +void wpa_ft_install_ptk(struct wpa_state_machine *sm); +#endif /* CONFIG_IEEE80211R */ + +#endif /* WPA_AUTH_I_H */ diff --git a/freebsd/contrib/wpa/src/ap/wpa_auth_ie.h b/freebsd/contrib/wpa/src/ap/wpa_auth_ie.h new file mode 100644 index 00000000..d2067ba3 --- /dev/null +++ b/freebsd/contrib/wpa/src/ap/wpa_auth_ie.h @@ -0,0 +1,57 @@ +/* + * hostapd - WPA/RSN IE and KDE definitions + * Copyright (c) 2004-2007, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_IE_H +#define WPA_AUTH_IE_H + +struct wpa_eapol_ie_parse { + const u8 *wpa_ie; + size_t wpa_ie_len; + const u8 *rsn_ie; + size_t rsn_ie_len; + const u8 *pmkid; + const u8 *gtk; + size_t gtk_len; + const u8 *mac_addr; + size_t mac_addr_len; +#ifdef CONFIG_PEERKEY + const u8 *smk; + size_t smk_len; + const u8 *nonce; + size_t nonce_len; + const u8 *lifetime; + size_t lifetime_len; + const u8 *error; + size_t error_len; +#endif /* CONFIG_PEERKEY */ +#ifdef CONFIG_IEEE80211W + const u8 *igtk; + size_t igtk_len; +#endif /* CONFIG_IEEE80211W */ +#ifdef CONFIG_IEEE80211R + const u8 *mdie; + size_t mdie_len; + const u8 *ftie; + size_t ftie_len; +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_P2P + const u8 *ip_addr_req; + const u8 *ip_addr_alloc; +#endif /* CONFIG_P2P */ + + const u8 *osen; + size_t osen_len; +}; + +int wpa_parse_kde_ies(const u8 *buf, size_t len, + struct wpa_eapol_ie_parse *ie); +u8 * wpa_add_kde(u8 *pos, u32 kde, const u8 *data, size_t data_len, + const u8 *data2, size_t data2_len); +int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth); + +#endif /* WPA_AUTH_IE_H */ diff --git a/freebsd/contrib/wpa/src/eapol_auth/eapol_auth_sm.h b/freebsd/contrib/wpa/src/eapol_auth/eapol_auth_sm.h new file mode 100644 index 00000000..e1974e43 --- /dev/null +++ b/freebsd/contrib/wpa/src/eapol_auth/eapol_auth_sm.h @@ -0,0 +1,102 @@ +/* + * IEEE 802.1X-2004 Authenticator - EAPOL state machine + * Copyright (c) 2002-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef EAPOL_AUTH_SM_H +#define EAPOL_AUTH_SM_H + +#define EAPOL_SM_PREAUTH BIT(0) +#define EAPOL_SM_WAIT_START BIT(1) +#define EAPOL_SM_USES_WPA BIT(2) +#define EAPOL_SM_FROM_PMKSA_CACHE BIT(3) + +struct eapol_auth_config { + int eap_reauth_period; + int wpa; + int individual_wep_key_len; + int eap_server; + void *ssl_ctx; + void *msg_ctx; + void *eap_sim_db_priv; + char *eap_req_id_text; /* a copy of this will be allocated */ + size_t eap_req_id_text_len; + int erp_send_reauth_start; + char *erp_domain; /* a copy of this will be allocated */ + int erp; /* Whether ERP is enabled on authentication server */ + unsigned int tls_session_lifetime; + u8 *pac_opaque_encr_key; + u8 *eap_fast_a_id; + size_t eap_fast_a_id_len; + char *eap_fast_a_id_info; + int eap_fast_prov; + int pac_key_lifetime; + int pac_key_refresh_time; + int eap_sim_aka_result_ind; + int tnc; + struct wps_context *wps; + int fragment_size; + u16 pwd_group; + int pbc_in_m1; + const u8 *server_id; + size_t server_id_len; + + /* Opaque context pointer to owner data for callback functions */ + void *ctx; +}; + +struct eap_user; +struct eap_server_erp_key; + +typedef enum { + EAPOL_LOGGER_DEBUG, EAPOL_LOGGER_INFO, EAPOL_LOGGER_WARNING +} eapol_logger_level; + +enum eapol_event { + EAPOL_AUTH_SM_CHANGE, + EAPOL_AUTH_REAUTHENTICATE +}; + +struct eapol_auth_cb { + void (*eapol_send)(void *ctx, void *sta_ctx, u8 type, const u8 *data, + size_t datalen); + void (*aaa_send)(void *ctx, void *sta_ctx, const u8 *data, + size_t datalen); + void (*finished)(void *ctx, void *sta_ctx, int success, int preauth, + int remediation); + int (*get_eap_user)(void *ctx, const u8 *identity, size_t identity_len, + int phase2, struct eap_user *user); + int (*sta_entry_alive)(void *ctx, const u8 *addr); + void (*logger)(void *ctx, const u8 *addr, eapol_logger_level level, + const char *txt); + void (*set_port_authorized)(void *ctx, void *sta_ctx, int authorized); + void (*abort_auth)(void *ctx, void *sta_ctx); + void (*tx_key)(void *ctx, void *sta_ctx); + void (*eapol_event)(void *ctx, void *sta_ctx, enum eapol_event type); + struct eap_server_erp_key * (*erp_get_key)(void *ctx, + const char *keyname); + int (*erp_add_key)(void *ctx, struct eap_server_erp_key *erp); +}; + + +struct eapol_authenticator * eapol_auth_init(struct eapol_auth_config *conf, + struct eapol_auth_cb *cb); +void eapol_auth_deinit(struct eapol_authenticator *eapol); +struct eapol_state_machine * +eapol_auth_alloc(struct eapol_authenticator *eapol, const u8 *addr, + int flags, const struct wpabuf *assoc_wps_ie, + const struct wpabuf *assoc_p2p_ie, void *sta_ctx, + const char *identity, const char *radius_cui); +void eapol_auth_free(struct eapol_state_machine *sm); +void eapol_auth_step(struct eapol_state_machine *sm); +int eapol_auth_dump_state(struct eapol_state_machine *sm, char *buf, + size_t buflen); +int eapol_auth_eap_pending_cb(struct eapol_state_machine *sm, void *ctx); +void eapol_auth_reauthenticate(struct eapol_state_machine *sm); +int eapol_auth_set_conf(struct eapol_state_machine *sm, const char *param, + const char *value); + +#endif /* EAPOL_AUTH_SM_H */ diff --git a/freebsd/contrib/wpa/src/radius/radius.h b/freebsd/contrib/wpa/src/radius/radius.h new file mode 100644 index 00000000..5977339e --- /dev/null +++ b/freebsd/contrib/wpa/src/radius/radius.h @@ -0,0 +1,322 @@ +/* + * RADIUS message processing + * Copyright (c) 2002-2009, 2012, 2014, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef RADIUS_H +#define RADIUS_H + +/* RFC 2865 - RADIUS */ + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif /* _MSC_VER */ + +struct radius_hdr { + u8 code; + u8 identifier; + be16 length; /* including this header */ + u8 authenticator[16]; + /* followed by length-20 octets of attributes */ +} STRUCT_PACKED; + +enum { RADIUS_CODE_ACCESS_REQUEST = 1, + RADIUS_CODE_ACCESS_ACCEPT = 2, + RADIUS_CODE_ACCESS_REJECT = 3, + RADIUS_CODE_ACCOUNTING_REQUEST = 4, + RADIUS_CODE_ACCOUNTING_RESPONSE = 5, + RADIUS_CODE_ACCESS_CHALLENGE = 11, + RADIUS_CODE_STATUS_SERVER = 12, + RADIUS_CODE_STATUS_CLIENT = 13, + RADIUS_CODE_DISCONNECT_REQUEST = 40, + RADIUS_CODE_DISCONNECT_ACK = 41, + RADIUS_CODE_DISCONNECT_NAK = 42, + RADIUS_CODE_COA_REQUEST = 43, + RADIUS_CODE_COA_ACK = 44, + RADIUS_CODE_COA_NAK = 45, + RADIUS_CODE_RESERVED = 255 +}; + +struct radius_attr_hdr { + u8 type; + u8 length; /* including this header */ + /* followed by length-2 octets of attribute value */ +} STRUCT_PACKED; + +#define RADIUS_MAX_ATTR_LEN (255 - sizeof(struct radius_attr_hdr)) + +enum { RADIUS_ATTR_USER_NAME = 1, + RADIUS_ATTR_USER_PASSWORD = 2, + RADIUS_ATTR_NAS_IP_ADDRESS = 4, + RADIUS_ATTR_NAS_PORT = 5, + RADIUS_ATTR_FRAMED_MTU = 12, + RADIUS_ATTR_REPLY_MESSAGE = 18, + RADIUS_ATTR_STATE = 24, + RADIUS_ATTR_CLASS = 25, + RADIUS_ATTR_VENDOR_SPECIFIC = 26, + RADIUS_ATTR_SESSION_TIMEOUT = 27, + RADIUS_ATTR_IDLE_TIMEOUT = 28, + RADIUS_ATTR_TERMINATION_ACTION = 29, + RADIUS_ATTR_CALLED_STATION_ID = 30, + RADIUS_ATTR_CALLING_STATION_ID = 31, + RADIUS_ATTR_NAS_IDENTIFIER = 32, + RADIUS_ATTR_PROXY_STATE = 33, + RADIUS_ATTR_ACCT_STATUS_TYPE = 40, + RADIUS_ATTR_ACCT_DELAY_TIME = 41, + RADIUS_ATTR_ACCT_INPUT_OCTETS = 42, + RADIUS_ATTR_ACCT_OUTPUT_OCTETS = 43, + RADIUS_ATTR_ACCT_SESSION_ID = 44, + RADIUS_ATTR_ACCT_AUTHENTIC = 45, + RADIUS_ATTR_ACCT_SESSION_TIME = 46, + RADIUS_ATTR_ACCT_INPUT_PACKETS = 47, + RADIUS_ATTR_ACCT_OUTPUT_PACKETS = 48, + RADIUS_ATTR_ACCT_TERMINATE_CAUSE = 49, + RADIUS_ATTR_ACCT_MULTI_SESSION_ID = 50, + RADIUS_ATTR_ACCT_LINK_COUNT = 51, + RADIUS_ATTR_ACCT_INPUT_GIGAWORDS = 52, + RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS = 53, + RADIUS_ATTR_EVENT_TIMESTAMP = 55, + RADIUS_ATTR_NAS_PORT_TYPE = 61, + RADIUS_ATTR_TUNNEL_TYPE = 64, + RADIUS_ATTR_TUNNEL_MEDIUM_TYPE = 65, + RADIUS_ATTR_TUNNEL_PASSWORD = 69, + RADIUS_ATTR_CONNECT_INFO = 77, + RADIUS_ATTR_EAP_MESSAGE = 79, + RADIUS_ATTR_MESSAGE_AUTHENTICATOR = 80, + RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID = 81, + RADIUS_ATTR_ACCT_INTERIM_INTERVAL = 85, + RADIUS_ATTR_CHARGEABLE_USER_IDENTITY = 89, + RADIUS_ATTR_NAS_IPV6_ADDRESS = 95, + RADIUS_ATTR_ERROR_CAUSE = 101, + RADIUS_ATTR_EAP_KEY_NAME = 102, + RADIUS_ATTR_OPERATOR_NAME = 126, + RADIUS_ATTR_LOCATION_INFO = 127, + RADIUS_ATTR_LOCATION_DATA = 128, + RADIUS_ATTR_BASIC_LOCATION_POLICY_RULES = 129, + RADIUS_ATTR_EXTENDED_LOCATION_POLICY_RULES = 130, + RADIUS_ATTR_LOCATION_CAPABLE = 131, + RADIUS_ATTR_REQUESTED_LOCATION_INFO = 132, + RADIUS_ATTR_MOBILITY_DOMAIN_ID = 177, + RADIUS_ATTR_WLAN_HESSID = 181, + RADIUS_ATTR_WLAN_PAIRWISE_CIPHER = 186, + RADIUS_ATTR_WLAN_GROUP_CIPHER = 187, + RADIUS_ATTR_WLAN_AKM_SUITE = 188, + RADIUS_ATTR_WLAN_GROUP_MGMT_CIPHER = 189, +}; + + +/* Termination-Action */ +#define RADIUS_TERMINATION_ACTION_DEFAULT 0 +#define RADIUS_TERMINATION_ACTION_RADIUS_REQUEST 1 + +/* NAS-Port-Type */ +#define RADIUS_NAS_PORT_TYPE_IEEE_802_11 19 + +/* Acct-Status-Type */ +#define RADIUS_ACCT_STATUS_TYPE_START 1 +#define RADIUS_ACCT_STATUS_TYPE_STOP 2 +#define RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE 3 +#define RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON 7 +#define RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF 8 + +/* Acct-Authentic */ +#define RADIUS_ACCT_AUTHENTIC_RADIUS 1 +#define RADIUS_ACCT_AUTHENTIC_LOCAL 2 +#define RADIUS_ACCT_AUTHENTIC_REMOTE 3 + +/* Acct-Terminate-Cause */ +#define RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST 1 +#define RADIUS_ACCT_TERMINATE_CAUSE_LOST_CARRIER 2 +#define RADIUS_ACCT_TERMINATE_CAUSE_LOST_SERVICE 3 +#define RADIUS_ACCT_TERMINATE_CAUSE_IDLE_TIMEOUT 4 +#define RADIUS_ACCT_TERMINATE_CAUSE_SESSION_TIMEOUT 5 +#define RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_RESET 6 +#define RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_REBOOT 7 +#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_ERROR 8 +#define RADIUS_ACCT_TERMINATE_CAUSE_NAS_ERROR 9 +#define RADIUS_ACCT_TERMINATE_CAUSE_NAS_REQUEST 10 +#define RADIUS_ACCT_TERMINATE_CAUSE_NAS_REBOOT 11 +#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_UNNEEDED 12 +#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_PREEMPTED 13 +#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_SUSPENDED 14 +#define RADIUS_ACCT_TERMINATE_CAUSE_SERVICE_UNAVAILABLE 15 +#define RADIUS_ACCT_TERMINATE_CAUSE_CALLBACK 16 +#define RADIUS_ACCT_TERMINATE_CAUSE_USER_ERROR 17 +#define RADIUS_ACCT_TERMINATE_CAUSE_HOST_REQUEST 18 + +#define RADIUS_TUNNEL_TAGS 32 + +/* Tunnel-Type */ +#define RADIUS_TUNNEL_TYPE_PPTP 1 +#define RADIUS_TUNNEL_TYPE_L2TP 3 +#define RADIUS_TUNNEL_TYPE_IPIP 7 +#define RADIUS_TUNNEL_TYPE_GRE 10 +#define RADIUS_TUNNEL_TYPE_VLAN 13 + +/* Tunnel-Medium-Type */ +#define RADIUS_TUNNEL_MEDIUM_TYPE_IPV4 1 +#define RADIUS_TUNNEL_MEDIUM_TYPE_IPV6 2 +#define RADIUS_TUNNEL_MEDIUM_TYPE_802 6 + + +struct radius_attr_vendor { + u8 vendor_type; + u8 vendor_length; +} STRUCT_PACKED; + +#define RADIUS_VENDOR_ID_CISCO 9 +#define RADIUS_CISCO_AV_PAIR 1 + +/* RFC 2548 - Microsoft Vendor-specific RADIUS Attributes */ +#define RADIUS_VENDOR_ID_MICROSOFT 311 + +enum { RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY = 16, + RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY = 17 +}; + + +/* Hotspot 2.0 - WFA Vendor-specific RADIUS Attributes */ +#define RADIUS_VENDOR_ID_WFA 40808 + +enum { + RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION = 1, + RADIUS_VENDOR_ATTR_WFA_HS20_AP_VERSION = 2, + RADIUS_VENDOR_ATTR_WFA_HS20_STA_VERSION = 3, + RADIUS_VENDOR_ATTR_WFA_HS20_DEAUTH_REQ = 4, + RADIUS_VENDOR_ATTR_WFA_HS20_SESSION_INFO_URL = 5, +}; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif /* _MSC_VER */ + +struct radius_ms_mppe_keys { + u8 *send; + size_t send_len; + u8 *recv; + size_t recv_len; +}; + + +struct radius_msg; + +/* Default size to be allocated for new RADIUS messages */ +#define RADIUS_DEFAULT_MSG_SIZE 1024 + +/* Default size to be allocated for attribute array */ +#define RADIUS_DEFAULT_ATTR_COUNT 16 + + +/* MAC address ASCII format for IEEE 802.1X use + * (draft-congdon-radius-8021x-20.txt) */ +#define RADIUS_802_1X_ADDR_FORMAT "%02X-%02X-%02X-%02X-%02X-%02X" +/* MAC address ASCII format for non-802.1X use */ +#define RADIUS_ADDR_FORMAT "%02x%02x%02x%02x%02x%02x" + +struct radius_hdr * radius_msg_get_hdr(struct radius_msg *msg); +struct wpabuf * radius_msg_get_buf(struct radius_msg *msg); +struct radius_msg * radius_msg_new(u8 code, u8 identifier); +void radius_msg_free(struct radius_msg *msg); +void radius_msg_dump(struct radius_msg *msg); +int radius_msg_finish(struct radius_msg *msg, const u8 *secret, + size_t secret_len); +int radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret, + size_t secret_len, const u8 *req_authenticator); +int radius_msg_finish_das_resp(struct radius_msg *msg, const u8 *secret, + size_t secret_len, + const struct radius_hdr *req_hdr); +void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret, + size_t secret_len); +void radius_msg_finish_acct_resp(struct radius_msg *msg, const u8 *secret, + size_t secret_len, + const u8 *req_authenticator); +int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret, + size_t secret_len); +int radius_msg_verify_das_req(struct radius_msg *msg, const u8 *secret, + size_t secret_len); +struct radius_attr_hdr * radius_msg_add_attr(struct radius_msg *msg, u8 type, + const u8 *data, size_t data_len); +struct radius_msg * radius_msg_parse(const u8 *data, size_t len); +int radius_msg_add_eap(struct radius_msg *msg, const u8 *data, + size_t data_len); +struct wpabuf * radius_msg_get_eap(struct radius_msg *msg); +int radius_msg_verify(struct radius_msg *msg, const u8 *secret, + size_t secret_len, struct radius_msg *sent_msg, + int auth); +int radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret, + size_t secret_len, const u8 *req_auth); +int radius_msg_copy_attr(struct radius_msg *dst, struct radius_msg *src, + u8 type); +void radius_msg_make_authenticator(struct radius_msg *msg, + const u8 *data, size_t len); +struct radius_ms_mppe_keys * +radius_msg_get_ms_keys(struct radius_msg *msg, struct radius_msg *sent_msg, + const u8 *secret, size_t secret_len); +struct radius_ms_mppe_keys * +radius_msg_get_cisco_keys(struct radius_msg *msg, struct radius_msg *sent_msg, + const u8 *secret, size_t secret_len); +int radius_msg_add_mppe_keys(struct radius_msg *msg, + const u8 *req_authenticator, + const u8 *secret, size_t secret_len, + const u8 *send_key, size_t send_key_len, + const u8 *recv_key, size_t recv_key_len); +int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data, + size_t len); +int radius_user_password_hide(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len, + u8 *buf, size_t buf_len); +struct radius_attr_hdr * +radius_msg_add_attr_user_password(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len); +int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len); +int radius_msg_get_vlanid(struct radius_msg *msg); +char * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen, + const u8 *secret, size_t secret_len, + struct radius_msg *sent_msg, size_t n); + +static inline int radius_msg_add_attr_int32(struct radius_msg *msg, u8 type, + u32 value) +{ + u32 val = htonl(value); + return radius_msg_add_attr(msg, type, (u8 *) &val, 4) != NULL; +} + +static inline int radius_msg_get_attr_int32(struct radius_msg *msg, u8 type, + u32 *value) +{ + u32 val; + int res; + res = radius_msg_get_attr(msg, type, (u8 *) &val, 4); + if (res != 4) + return -1; + + *value = ntohl(val); + return 0; +} +int radius_msg_get_attr_ptr(struct radius_msg *msg, u8 type, u8 **buf, + size_t *len, const u8 *start); +int radius_msg_count_attr(struct radius_msg *msg, u8 type, int min_len); + + +struct radius_attr_data { + u8 *data; + size_t len; +}; + +struct radius_class_data { + struct radius_attr_data *attr; + size_t count; +}; + +void radius_free_class(struct radius_class_data *c); +int radius_copy_class(struct radius_class_data *dst, + const struct radius_class_data *src); + +u8 radius_msg_find_unlisted_attr(struct radius_msg *msg, u8 *attrs); + +#endif /* RADIUS_H */ diff --git a/freebsd/contrib/wpa/src/rsn_supp/tdls.c b/freebsd/contrib/wpa/src/rsn_supp/tdls.c new file mode 100644 index 00000000..8d0359d1 --- /dev/null +++ b/freebsd/contrib/wpa/src/rsn_supp/tdls.c @@ -0,0 +1,3010 @@ +#include + +/* + * wpa_supplicant - TDLS + * Copyright (c) 2010-2011, Atheros Communications + * + * 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 "utils/eloop.h" +#include "utils/os.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "crypto/sha256.h" +#include "crypto/crypto.h" +#include "crypto/aes_wrap.h" +#include "rsn_supp/wpa.h" +#include "rsn_supp/wpa_ie.h" +#include "rsn_supp/wpa_i.h" +#include "drivers/driver.h" +#include "l2_packet/l2_packet.h" + +#ifdef CONFIG_TDLS_TESTING +#define TDLS_TESTING_LONG_FRAME BIT(0) +#define TDLS_TESTING_ALT_RSN_IE BIT(1) +#define TDLS_TESTING_DIFF_BSSID BIT(2) +#define TDLS_TESTING_SHORT_LIFETIME BIT(3) +#define TDLS_TESTING_WRONG_LIFETIME_RESP BIT(4) +#define TDLS_TESTING_WRONG_LIFETIME_CONF BIT(5) +#define TDLS_TESTING_LONG_LIFETIME BIT(6) +#define TDLS_TESTING_CONCURRENT_INIT BIT(7) +#define TDLS_TESTING_NO_TPK_EXPIRATION BIT(8) +#define TDLS_TESTING_DECLINE_RESP BIT(9) +#define TDLS_TESTING_IGNORE_AP_PROHIBIT BIT(10) +#define TDLS_TESTING_WRONG_MIC BIT(11) +unsigned int tdls_testing = 0; +#endif /* CONFIG_TDLS_TESTING */ + +#define TPK_LIFETIME 43200 /* 12 hours */ +#define TPK_M1_RETRY_COUNT 3 +#define TPK_M1_TIMEOUT 5000 /* in milliseconds */ +#define TPK_M2_RETRY_COUNT 10 +#define TPK_M2_TIMEOUT 500 /* in milliseconds */ + +#define TDLS_MIC_LEN 16 + +#define TDLS_TIMEOUT_LEN 4 + +struct wpa_tdls_ftie { + u8 ie_type; /* FTIE */ + u8 ie_len; + u8 mic_ctrl[2]; + u8 mic[TDLS_MIC_LEN]; + u8 Anonce[WPA_NONCE_LEN]; /* Responder Nonce in TDLS */ + u8 Snonce[WPA_NONCE_LEN]; /* Initiator Nonce in TDLS */ + /* followed by optional elements */ +} STRUCT_PACKED; + +struct wpa_tdls_timeoutie { + u8 ie_type; /* Timeout IE */ + u8 ie_len; + u8 interval_type; + u8 value[TDLS_TIMEOUT_LEN]; +} STRUCT_PACKED; + +struct wpa_tdls_lnkid { + u8 ie_type; /* Link Identifier IE */ + u8 ie_len; + u8 bssid[ETH_ALEN]; + u8 init_sta[ETH_ALEN]; + u8 resp_sta[ETH_ALEN]; +} STRUCT_PACKED; + +/* TDLS frame headers as per IEEE Std 802.11z-2010 */ +struct wpa_tdls_frame { + u8 payloadtype; /* IEEE80211_TDLS_RFTYPE */ + u8 category; /* Category */ + u8 action; /* Action (enum tdls_frame_type) */ +} STRUCT_PACKED; + +static u8 * wpa_add_tdls_timeoutie(u8 *pos, u8 *ie, size_t ie_len, u32 tsecs); +static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx); +static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer); +static void wpa_tdls_disable_peer_link(struct wpa_sm *sm, + struct wpa_tdls_peer *peer); +static int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, + u16 reason_code); + + +#define TDLS_MAX_IE_LEN 80 +#define IEEE80211_MAX_SUPP_RATES 32 + +struct wpa_tdls_peer { + struct wpa_tdls_peer *next; + unsigned int reconfig_key:1; + int initiator; /* whether this end was initiator for TDLS setup */ + u8 addr[ETH_ALEN]; /* other end MAC address */ + u8 inonce[WPA_NONCE_LEN]; /* Initiator Nonce */ + u8 rnonce[WPA_NONCE_LEN]; /* Responder Nonce */ + u8 rsnie_i[TDLS_MAX_IE_LEN]; /* Initiator RSN IE */ + size_t rsnie_i_len; + u8 rsnie_p[TDLS_MAX_IE_LEN]; /* Peer RSN IE */ + size_t rsnie_p_len; + u32 lifetime; + int cipher; /* Selected cipher (WPA_CIPHER_*) */ + u8 dtoken; + + struct tpk { + u8 kck[16]; /* TPK-KCK */ + u8 tk[16]; /* TPK-TK; assuming only CCMP will be used */ + } tpk; + int tpk_set; + int tpk_success; + int tpk_in_progress; + + struct tpk_timer { + u8 dest[ETH_ALEN]; + int count; /* Retry Count */ + int timer; /* Timeout in milliseconds */ + u8 action_code; /* TDLS frame type */ + u8 dialog_token; + u16 status_code; + u32 peer_capab; + int buf_len; /* length of TPK message for retransmission */ + u8 *buf; /* buffer for TPK message */ + } sm_tmr; + + u16 capability; + + u8 supp_rates[IEEE80211_MAX_SUPP_RATES]; + size_t supp_rates_len; + + struct ieee80211_ht_capabilities *ht_capabilities; + struct ieee80211_vht_capabilities *vht_capabilities; + + u8 qos_info; + + u16 aid; + + u8 *ext_capab; + size_t ext_capab_len; + + u8 *supp_channels; + size_t supp_channels_len; + + u8 *supp_oper_classes; + size_t supp_oper_classes_len; + + u8 wmm_capable; + + /* channel switch currently enabled */ + int chan_switch_enabled; +}; + + +static int wpa_tdls_get_privacy(struct wpa_sm *sm) +{ + /* + * Get info needed from supplicant to check if the current BSS supports + * security. Other than OPEN mode, rest are considered secured + * WEP/WPA/WPA2 hence TDLS frames are processed for TPK handshake. + */ + return sm->pairwise_cipher != WPA_CIPHER_NONE; +} + + +static u8 * wpa_add_ie(u8 *pos, const u8 *ie, size_t ie_len) +{ + os_memcpy(pos, ie, ie_len); + return pos + ie_len; +} + + +static int wpa_tdls_del_key(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +{ + if (wpa_sm_set_key(sm, WPA_ALG_NONE, peer->addr, + 0, 0, NULL, 0, NULL, 0) < 0) { + wpa_printf(MSG_WARNING, "TDLS: Failed to delete TPK-TK from " + "the driver"); + return -1; + } + + return 0; +} + + +static int wpa_tdls_set_key(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +{ + u8 key_len; + u8 rsc[6]; + enum wpa_alg alg; + + os_memset(rsc, 0, 6); + + switch (peer->cipher) { + case WPA_CIPHER_CCMP: + alg = WPA_ALG_CCMP; + key_len = 16; + break; + case WPA_CIPHER_NONE: + wpa_printf(MSG_DEBUG, "TDLS: Pairwise Cipher Suite: " + "NONE - do not use pairwise keys"); + return -1; + default: + wpa_printf(MSG_WARNING, "TDLS: Unsupported pairwise cipher %d", + sm->pairwise_cipher); + return -1; + } + + if (wpa_sm_set_key(sm, alg, peer->addr, -1, 1, + rsc, sizeof(rsc), peer->tpk.tk, key_len) < 0) { + wpa_printf(MSG_WARNING, "TDLS: Failed to set TPK to the " + "driver"); + return -1; + } + return 0; +} + + +static int wpa_tdls_send_tpk_msg(struct wpa_sm *sm, const u8 *dst, + u8 action_code, u8 dialog_token, + u16 status_code, u32 peer_capab, + int initiator, const u8 *buf, size_t len) +{ + return wpa_sm_send_tdls_mgmt(sm, dst, action_code, dialog_token, + status_code, peer_capab, initiator, buf, + len); +} + + +static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, + u8 dialog_token, u16 status_code, u32 peer_capab, + int initiator, const u8 *msg, size_t msg_len) +{ + struct wpa_tdls_peer *peer; + + wpa_printf(MSG_DEBUG, "TDLS: TPK send dest=" MACSTR " action_code=%u " + "dialog_token=%u status_code=%u peer_capab=%u initiator=%d " + "msg_len=%u", + MAC2STR(dest), action_code, dialog_token, status_code, + peer_capab, initiator, (unsigned int) msg_len); + + if (wpa_tdls_send_tpk_msg(sm, dest, action_code, dialog_token, + status_code, peer_capab, initiator, msg, + msg_len)) { + wpa_printf(MSG_INFO, "TDLS: Failed to send message " + "(action_code=%u)", action_code); + return -1; + } + + if (action_code == WLAN_TDLS_SETUP_CONFIRM || + action_code == WLAN_TDLS_TEARDOWN || + action_code == WLAN_TDLS_DISCOVERY_REQUEST || + action_code == WLAN_TDLS_DISCOVERY_RESPONSE) + return 0; /* No retries */ + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, dest, ETH_ALEN) == 0) + break; + } + + if (peer == NULL) { + wpa_printf(MSG_INFO, "TDLS: No matching entry found for " + "retry " MACSTR, MAC2STR(dest)); + return 0; + } + + eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); + + if (action_code == WLAN_TDLS_SETUP_RESPONSE) { + peer->sm_tmr.count = TPK_M2_RETRY_COUNT; + peer->sm_tmr.timer = TPK_M2_TIMEOUT; + } else { + peer->sm_tmr.count = TPK_M1_RETRY_COUNT; + peer->sm_tmr.timer = TPK_M1_TIMEOUT; + } + + /* Copy message to resend on timeout */ + os_memcpy(peer->sm_tmr.dest, dest, ETH_ALEN); + peer->sm_tmr.action_code = action_code; + peer->sm_tmr.dialog_token = dialog_token; + peer->sm_tmr.status_code = status_code; + peer->sm_tmr.peer_capab = peer_capab; + peer->sm_tmr.buf_len = msg_len; + os_free(peer->sm_tmr.buf); + peer->sm_tmr.buf = os_malloc(msg_len); + if (peer->sm_tmr.buf == NULL) + return -1; + os_memcpy(peer->sm_tmr.buf, msg, msg_len); + + wpa_printf(MSG_DEBUG, "TDLS: Retry timeout registered " + "(action_code=%u)", action_code); + eloop_register_timeout(peer->sm_tmr.timer / 1000, + (peer->sm_tmr.timer % 1000) * 1000, + wpa_tdls_tpk_retry_timeout, sm, peer); + return 0; +} + + +static int wpa_tdls_do_teardown(struct wpa_sm *sm, struct wpa_tdls_peer *peer, + u16 reason_code) +{ + int ret; + + ret = wpa_tdls_send_teardown(sm, peer->addr, reason_code); + /* disable the link after teardown was sent */ + wpa_tdls_disable_peer_link(sm, peer); + + return ret; +} + + +static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx) +{ + + struct wpa_sm *sm = eloop_ctx; + struct wpa_tdls_peer *peer = timeout_ctx; + + if (peer->sm_tmr.count) { + peer->sm_tmr.count--; + + wpa_printf(MSG_INFO, "TDLS: Retrying sending of message " + "(action_code=%u)", + peer->sm_tmr.action_code); + + if (peer->sm_tmr.buf == NULL) { + wpa_printf(MSG_INFO, "TDLS: No retry buffer available " + "for action_code=%u", + peer->sm_tmr.action_code); + eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, + peer); + return; + } + + /* resend TPK Handshake Message to Peer */ + if (wpa_tdls_send_tpk_msg(sm, peer->sm_tmr.dest, + peer->sm_tmr.action_code, + peer->sm_tmr.dialog_token, + peer->sm_tmr.status_code, + peer->sm_tmr.peer_capab, + peer->initiator, + peer->sm_tmr.buf, + peer->sm_tmr.buf_len)) { + wpa_printf(MSG_INFO, "TDLS: Failed to retry " + "transmission"); + } + + eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); + eloop_register_timeout(peer->sm_tmr.timer / 1000, + (peer->sm_tmr.timer % 1000) * 1000, + wpa_tdls_tpk_retry_timeout, sm, peer); + } else { + eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); + + wpa_printf(MSG_DEBUG, "TDLS: Sending Teardown Request"); + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + } +} + + +static void wpa_tdls_tpk_retry_timeout_cancel(struct wpa_sm *sm, + struct wpa_tdls_peer *peer, + u8 action_code) +{ + if (action_code == peer->sm_tmr.action_code) { + wpa_printf(MSG_DEBUG, "TDLS: Retry timeout cancelled for " + "action_code=%u", action_code); + + /* Cancel Timeout registered */ + eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); + + /* free all resources meant for retry */ + os_free(peer->sm_tmr.buf); + peer->sm_tmr.buf = NULL; + + peer->sm_tmr.count = 0; + peer->sm_tmr.timer = 0; + peer->sm_tmr.buf_len = 0; + peer->sm_tmr.action_code = 0xff; + } else { + wpa_printf(MSG_INFO, "TDLS: Error in cancelling retry timeout " + "(Unknown action_code=%u)", action_code); + } +} + + +static void wpa_tdls_generate_tpk(struct wpa_tdls_peer *peer, + const u8 *own_addr, const u8 *bssid) +{ + u8 key_input[SHA256_MAC_LEN]; + const u8 *nonce[2]; + size_t len[2]; + u8 data[3 * ETH_ALEN]; + + /* 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(peer->inonce, peer->rnonce, WPA_NONCE_LEN) < 0) { + nonce[0] = peer->inonce; + nonce[1] = peer->rnonce; + } else { + nonce[0] = peer->rnonce; + nonce[1] = peer->inonce; + } + wpa_hexdump(MSG_DEBUG, "TDLS: min(Nonce)", nonce[0], WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "TDLS: max(Nonce)", nonce[1], WPA_NONCE_LEN); + 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(own_addr, peer->addr, ETH_ALEN) < 0) { + os_memcpy(data, own_addr, ETH_ALEN); + os_memcpy(data + ETH_ALEN, peer->addr, ETH_ALEN); + } else { + os_memcpy(data, peer->addr, ETH_ALEN); + os_memcpy(data + ETH_ALEN, own_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 *) &peer->tpk, sizeof(peer->tpk)); + wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-KCK", + peer->tpk.kck, sizeof(peer->tpk.kck)); + wpa_hexdump_key(MSG_DEBUG, "TDLS: TPK-TK", + peer->tpk.tk, sizeof(peer->tpk.tk)); + peer->tpk_set = 1; +} + + +/** + * wpa_tdls_ftie_mic - Calculate TDLS FTIE MIC + * @kck: TPK-KCK + * @lnkid: Pointer to the beginning of Link Identifier IE + * @rsnie: Pointer to the beginning of RSN IE used for handshake + * @timeoutie: Pointer to the beginning of Timeout IE used for handshake + * @ftie: Pointer to the beginning of FT IE + * @mic: Pointer for writing MIC + * + * Calculate MIC for TDLS frame. + */ +static int wpa_tdls_ftie_mic(const u8 *kck, u8 trans_seq, const u8 *lnkid, + const u8 *rsnie, const u8 *timeoutie, + const u8 *ftie, u8 *mic) +{ + u8 *buf, *pos; + struct wpa_tdls_ftie *_ftie; + const struct wpa_tdls_lnkid *_lnkid; + int ret; + int len = 2 * ETH_ALEN + 1 + 2 + lnkid[1] + 2 + rsnie[1] + + 2 + timeoutie[1] + 2 + ftie[1]; + buf = os_zalloc(len); + if (!buf) { + wpa_printf(MSG_WARNING, "TDLS: No memory for MIC calculation"); + return -1; + } + + pos = buf; + _lnkid = (const struct wpa_tdls_lnkid *) lnkid; + /* 1) TDLS initiator STA MAC address */ + os_memcpy(pos, _lnkid->init_sta, ETH_ALEN); + pos += ETH_ALEN; + /* 2) TDLS responder STA MAC address */ + os_memcpy(pos, _lnkid->resp_sta, ETH_ALEN); + pos += ETH_ALEN; + /* 3) Transaction Sequence number */ + *pos++ = trans_seq; + /* 4) Link Identifier IE */ + os_memcpy(pos, lnkid, 2 + lnkid[1]); + pos += 2 + lnkid[1]; + /* 5) RSN IE */ + os_memcpy(pos, rsnie, 2 + rsnie[1]); + pos += 2 + rsnie[1]; + /* 6) Timeout Interval IE */ + os_memcpy(pos, timeoutie, 2 + timeoutie[1]); + pos += 2 + timeoutie[1]; + /* 7) FTIE, with the MIC field of the FTIE set to 0 */ + os_memcpy(pos, ftie, 2 + ftie[1]); + _ftie = (struct wpa_tdls_ftie *) pos; + os_memset(_ftie->mic, 0, TDLS_MIC_LEN); + pos += 2 + ftie[1]; + + wpa_hexdump(MSG_DEBUG, "TDLS: Data for FTIE MIC", buf, pos - buf); + wpa_hexdump_key(MSG_DEBUG, "TDLS: KCK", kck, 16); + ret = omac1_aes_128(kck, buf, pos - buf, mic); + os_free(buf); + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE MIC", mic, 16); + return ret; +} + + +/** + * wpa_tdls_key_mic_teardown - Calculate TDLS FTIE MIC for Teardown frame + * @kck: TPK-KCK + * @trans_seq: Transaction Sequence Number (4 - Teardown) + * @rcode: Reason code for Teardown + * @dtoken: Dialog Token used for that particular link + * @lnkid: Pointer to the beginning of Link Identifier IE + * @ftie: Pointer to the beginning of FT IE + * @mic: Pointer for writing MIC + * + * Calculate MIC for TDLS frame. + */ +static int wpa_tdls_key_mic_teardown(const u8 *kck, u8 trans_seq, u16 rcode, + u8 dtoken, const u8 *lnkid, + const u8 *ftie, u8 *mic) +{ + u8 *buf, *pos; + struct wpa_tdls_ftie *_ftie; + int ret; + int len; + + if (lnkid == NULL) + return -1; + + len = 2 + lnkid[1] + sizeof(rcode) + sizeof(dtoken) + + sizeof(trans_seq) + 2 + ftie[1]; + + buf = os_zalloc(len); + if (!buf) { + wpa_printf(MSG_WARNING, "TDLS: No memory for MIC calculation"); + return -1; + } + + pos = buf; + /* 1) Link Identifier IE */ + os_memcpy(pos, lnkid, 2 + lnkid[1]); + pos += 2 + lnkid[1]; + /* 2) Reason Code */ + WPA_PUT_LE16(pos, rcode); + pos += sizeof(rcode); + /* 3) Dialog token */ + *pos++ = dtoken; + /* 4) Transaction Sequence number */ + *pos++ = trans_seq; + /* 7) FTIE, with the MIC field of the FTIE set to 0 */ + os_memcpy(pos, ftie, 2 + ftie[1]); + _ftie = (struct wpa_tdls_ftie *) pos; + os_memset(_ftie->mic, 0, TDLS_MIC_LEN); + pos += 2 + ftie[1]; + + wpa_hexdump(MSG_DEBUG, "TDLS: Data for FTIE MIC", buf, pos - buf); + wpa_hexdump_key(MSG_DEBUG, "TDLS: KCK", kck, 16); + ret = omac1_aes_128(kck, buf, pos - buf, mic); + os_free(buf); + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE MIC", mic, 16); + return ret; +} + + +static int wpa_supplicant_verify_tdls_mic(u8 trans_seq, + struct wpa_tdls_peer *peer, + const u8 *lnkid, const u8 *timeoutie, + const struct wpa_tdls_ftie *ftie) +{ + u8 mic[16]; + + if (peer->tpk_set) { + wpa_tdls_ftie_mic(peer->tpk.kck, trans_seq, lnkid, + peer->rsnie_p, timeoutie, (u8 *) ftie, + mic); + if (os_memcmp_const(mic, ftie->mic, 16) != 0) { + wpa_printf(MSG_INFO, "TDLS: Invalid MIC in FTIE - " + "dropping packet"); + wpa_hexdump(MSG_DEBUG, "TDLS: Received MIC", + ftie->mic, 16); + wpa_hexdump(MSG_DEBUG, "TDLS: Calculated MIC", + mic, 16); + return -1; + } + } else { + wpa_printf(MSG_WARNING, "TDLS: Could not verify TDLS MIC, " + "TPK not set - dropping packet"); + return -1; + } + return 0; +} + + +static int wpa_supplicant_verify_tdls_mic_teardown( + u8 trans_seq, u16 rcode, u8 dtoken, struct wpa_tdls_peer *peer, + const u8 *lnkid, const struct wpa_tdls_ftie *ftie) +{ + u8 mic[16]; + + if (peer->tpk_set) { + wpa_tdls_key_mic_teardown(peer->tpk.kck, trans_seq, rcode, + dtoken, lnkid, (u8 *) ftie, mic); + if (os_memcmp_const(mic, ftie->mic, 16) != 0) { + wpa_printf(MSG_INFO, "TDLS: Invalid MIC in Teardown - " + "dropping packet"); + return -1; + } + } else { + wpa_printf(MSG_INFO, "TDLS: Could not verify TDLS Teardown " + "MIC, TPK not set - dropping packet"); + return -1; + } + return 0; +} + + +static void wpa_tdls_tpk_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_sm *sm = eloop_ctx; + struct wpa_tdls_peer *peer = timeout_ctx; + + /* + * On TPK lifetime expiration, we have an option of either tearing down + * the direct link or trying to re-initiate it. The selection of what + * to do is not strictly speaking controlled by our role in the expired + * link, but for now, use that to select whether to renew or tear down + * the link. + */ + + if (peer->initiator) { + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime expired for " MACSTR + " - try to renew", MAC2STR(peer->addr)); + wpa_tdls_start(sm, peer->addr); + } else { + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime expired for " MACSTR + " - tear down", MAC2STR(peer->addr)); + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + } +} + + +static void wpa_tdls_peer_remove_from_list(struct wpa_sm *sm, + struct wpa_tdls_peer *peer) +{ + struct wpa_tdls_peer *cur, *prev; + + cur = sm->tdls; + prev = NULL; + while (cur && cur != peer) { + prev = cur; + cur = cur->next; + } + + if (cur != peer) { + wpa_printf(MSG_ERROR, "TDLS: Could not find peer " MACSTR + " to remove it from the list", + MAC2STR(peer->addr)); + return; + } + + if (prev) + prev->next = peer->next; + else + sm->tdls = peer->next; +} + + +static void wpa_tdls_peer_clear(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +{ + wpa_printf(MSG_DEBUG, "TDLS: Clear state for peer " MACSTR, + MAC2STR(peer->addr)); + eloop_cancel_timeout(wpa_tdls_tpk_timeout, sm, peer); + eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); + peer->reconfig_key = 0; + peer->initiator = 0; + peer->tpk_in_progress = 0; + os_free(peer->sm_tmr.buf); + peer->sm_tmr.buf = NULL; + os_free(peer->ht_capabilities); + peer->ht_capabilities = NULL; + os_free(peer->vht_capabilities); + peer->vht_capabilities = NULL; + os_free(peer->ext_capab); + peer->ext_capab = NULL; + os_free(peer->supp_channels); + peer->supp_channels = NULL; + os_free(peer->supp_oper_classes); + peer->supp_oper_classes = NULL; + peer->rsnie_i_len = peer->rsnie_p_len = 0; + peer->cipher = 0; + peer->qos_info = 0; + peer->wmm_capable = 0; + peer->tpk_set = peer->tpk_success = 0; + peer->chan_switch_enabled = 0; + os_memset(&peer->tpk, 0, sizeof(peer->tpk)); + os_memset(peer->inonce, 0, WPA_NONCE_LEN); + os_memset(peer->rnonce, 0, WPA_NONCE_LEN); +} + + +static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +{ + wpa_tdls_peer_clear(sm, peer); + wpa_tdls_peer_remove_from_list(sm, peer); + os_free(peer); +} + + +static void wpa_tdls_linkid(struct wpa_sm *sm, struct wpa_tdls_peer *peer, + struct wpa_tdls_lnkid *lnkid) +{ + lnkid->ie_type = WLAN_EID_LINK_ID; + lnkid->ie_len = 3 * ETH_ALEN; + os_memcpy(lnkid->bssid, sm->bssid, ETH_ALEN); + if (peer->initiator) { + os_memcpy(lnkid->init_sta, sm->own_addr, ETH_ALEN); + os_memcpy(lnkid->resp_sta, peer->addr, ETH_ALEN); + } else { + os_memcpy(lnkid->init_sta, peer->addr, ETH_ALEN); + os_memcpy(lnkid->resp_sta, sm->own_addr, ETH_ALEN); + } +} + + +static int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, + u16 reason_code) +{ + struct wpa_tdls_peer *peer; + struct wpa_tdls_ftie *ftie; + struct wpa_tdls_lnkid lnkid; + u8 dialog_token; + u8 *rbuf, *pos; + int ielen; + + if (sm->tdls_disabled || !sm->tdls_supported) + return -1; + + /* Find the node and free from the list */ + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (peer == NULL) { + wpa_printf(MSG_INFO, "TDLS: No matching entry found for " + "Teardown " MACSTR, MAC2STR(addr)); + return 0; + } + + /* Cancel active channel switch before teardown */ + if (peer->chan_switch_enabled) { + wpa_printf(MSG_DEBUG, "TDLS: First returning link with " MACSTR + " to base channel", MAC2STR(addr)); + wpa_sm_tdls_disable_channel_switch(sm, peer->addr); + } + + dialog_token = peer->dtoken; + + wpa_printf(MSG_DEBUG, "TDLS: TDLS Teardown for " MACSTR, + MAC2STR(addr)); + + ielen = 0; + if (wpa_tdls_get_privacy(sm) && peer->tpk_set && peer->tpk_success) { + /* To add FTIE for Teardown request and compute MIC */ + ielen += sizeof(*ftie); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_LONG_FRAME) + ielen += 170; +#endif /* CONFIG_TDLS_TESTING */ + } + + rbuf = os_zalloc(ielen + 1); + if (rbuf == NULL) + return -1; + pos = rbuf; + + if (!wpa_tdls_get_privacy(sm) || !peer->tpk_set || !peer->tpk_success) + goto skip_ies; + + ftie = (struct wpa_tdls_ftie *) pos; + ftie->ie_type = WLAN_EID_FAST_BSS_TRANSITION; + /* Using the recent nonce which should be for CONFIRM frame */ + os_memcpy(ftie->Anonce, peer->rnonce, WPA_NONCE_LEN); + os_memcpy(ftie->Snonce, peer->inonce, WPA_NONCE_LEN); + ftie->ie_len = sizeof(struct wpa_tdls_ftie) - 2; + pos = (u8 *) (ftie + 1); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_LONG_FRAME) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - add extra subelem to " + "FTIE"); + ftie->ie_len += 170; + *pos++ = 255; /* FTIE subelem */ + *pos++ = 168; /* FTIE subelem length */ + pos += 168; + } +#endif /* CONFIG_TDLS_TESTING */ + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE for TDLS Teardown handshake", + (u8 *) ftie, pos - (u8 *) ftie); + + /* compute MIC before sending */ + wpa_tdls_linkid(sm, peer, &lnkid); + wpa_tdls_key_mic_teardown(peer->tpk.kck, 4, reason_code, + dialog_token, (u8 *) &lnkid, (u8 *) ftie, + ftie->mic); + +skip_ies: + /* TODO: register for a Timeout handler, if Teardown is not received at + * the other end, then try again another time */ + + /* request driver to send Teardown using this FTIE */ + wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_TEARDOWN, 0, + reason_code, 0, peer->initiator, rbuf, pos - rbuf); + os_free(rbuf); + + return 0; +} + + +int wpa_tdls_teardown_link(struct wpa_sm *sm, const u8 *addr, u16 reason_code) +{ + struct wpa_tdls_peer *peer; + + if (sm->tdls_disabled || !sm->tdls_supported) + return -1; + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (peer == NULL) { + wpa_printf(MSG_DEBUG, "TDLS: Could not find peer " MACSTR + " for link Teardown", MAC2STR(addr)); + return -1; + } + + if (!peer->tpk_success) { + wpa_printf(MSG_DEBUG, "TDLS: Peer " MACSTR + " not connected - cannot Teardown link", MAC2STR(addr)); + return -1; + } + + return wpa_tdls_do_teardown(sm, peer, reason_code); +} + + +static void wpa_tdls_disable_peer_link(struct wpa_sm *sm, + struct wpa_tdls_peer *peer) +{ + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); + wpa_tdls_peer_free(sm, peer); +} + + +void wpa_tdls_disable_unreachable_link(struct wpa_sm *sm, const u8 *addr) +{ + struct wpa_tdls_peer *peer; + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (!peer || !peer->tpk_success) { + wpa_printf(MSG_DEBUG, "TDLS: Peer " MACSTR + " not connected - cannot teardown unreachable link", + MAC2STR(addr)); + return; + } + + if (wpa_tdls_is_external_setup(sm)) { + /* + * Get us on the base channel, disable the link, send a + * teardown packet through the AP, and then reset link data. + */ + if (peer->chan_switch_enabled) + wpa_sm_tdls_disable_channel_switch(sm, peer->addr); + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, addr); + wpa_tdls_send_teardown(sm, addr, + WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE); + wpa_tdls_peer_free(sm, peer); + } else { + wpa_tdls_disable_peer_link(sm, peer); + } +} + + +const char * wpa_tdls_get_link_status(struct wpa_sm *sm, const u8 *addr) +{ + struct wpa_tdls_peer *peer; + + if (sm->tdls_disabled || !sm->tdls_supported) + return "disabled"; + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (peer == NULL) + return "peer does not exist"; + + if (!peer->tpk_success) + return "peer not connected"; + + return "connected"; +} + + +static int wpa_tdls_recv_teardown(struct wpa_sm *sm, const u8 *src_addr, + const u8 *buf, size_t len) +{ + struct wpa_tdls_peer *peer = NULL; + struct wpa_tdls_ftie *ftie; + struct wpa_tdls_lnkid *lnkid; + struct wpa_eapol_ie_parse kde; + u16 reason_code; + const u8 *pos; + int ielen; + + /* Find the node and free from the list */ + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0) + break; + } + + if (peer == NULL) { + wpa_printf(MSG_INFO, "TDLS: No matching entry found for " + "Teardown " MACSTR, MAC2STR(src_addr)); + return 0; + } + + pos = buf; + pos += 1 /* pkt_type */ + 1 /* Category */ + 1 /* Action */; + + reason_code = WPA_GET_LE16(pos); + pos += 2; + + wpa_printf(MSG_DEBUG, "TDLS: TDLS Teardown Request from " MACSTR + " (reason code %u)", MAC2STR(src_addr), reason_code); + + ielen = len - (pos - buf); /* start of IE in buf */ + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs may add arbitrary padding to the + * end of short TDLS frames and that would look like invalid IEs. + */ + if (wpa_supplicant_parse_ies((const u8 *) pos, ielen, &kde) < 0) + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in Teardown - ignore as an interop workaround"); + + if (kde.lnkid == NULL || kde.lnkid_len < 3 * ETH_ALEN) { + wpa_printf(MSG_INFO, "TDLS: No Link Identifier IE in TDLS " + "Teardown"); + return -1; + } + lnkid = (struct wpa_tdls_lnkid *) kde.lnkid; + + if (!wpa_tdls_get_privacy(sm) || !peer->tpk_set || !peer->tpk_success) + goto skip_ftie; + + if (kde.ftie == NULL || kde.ftie_len < sizeof(*ftie)) { + wpa_printf(MSG_INFO, "TDLS: No FTIE in TDLS Teardown"); + return -1; + } + + ftie = (struct wpa_tdls_ftie *) kde.ftie; + + /* Process MIC check to see if TDLS Teardown is right */ + if (wpa_supplicant_verify_tdls_mic_teardown(4, reason_code, + peer->dtoken, peer, + (u8 *) lnkid, ftie) < 0) { + wpa_printf(MSG_DEBUG, "TDLS: MIC failure for TDLS " + "Teardown Request from " MACSTR, MAC2STR(src_addr)); + return -1; + } + +skip_ftie: + /* + * Request the driver to disable the direct link and clear associated + * keys. + */ + wpa_tdls_disable_peer_link(sm, peer); + return 0; +} + + +/** + * wpa_tdls_send_error - To send suitable TDLS status response with + * appropriate status code mentioning reason for error/failure. + * @dst - MAC addr of Peer station + * @tdls_action - TDLS frame type for which error code is sent + * @initiator - was this end the initiator of the connection + * @status - status code mentioning reason + */ + +static int wpa_tdls_send_error(struct wpa_sm *sm, const u8 *dst, + u8 tdls_action, u8 dialog_token, int initiator, + u16 status) +{ + wpa_printf(MSG_DEBUG, "TDLS: Sending error to " MACSTR + " (action=%u status=%u)", + MAC2STR(dst), tdls_action, status); + return wpa_tdls_tpk_send(sm, dst, tdls_action, dialog_token, status, + 0, initiator, NULL, 0); +} + + +static struct wpa_tdls_peer * +wpa_tdls_add_peer(struct wpa_sm *sm, const u8 *addr, int *existing) +{ + struct wpa_tdls_peer *peer; + + if (existing) + *existing = 0; + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) { + if (existing) + *existing = 1; + return peer; /* re-use existing entry */ + } + } + + wpa_printf(MSG_INFO, "TDLS: Creating peer entry for " MACSTR, + MAC2STR(addr)); + + peer = os_zalloc(sizeof(*peer)); + if (peer == NULL) + return NULL; + + os_memcpy(peer->addr, addr, ETH_ALEN); + peer->next = sm->tdls; + sm->tdls = peer; + + return peer; +} + + +static int wpa_tdls_send_tpk_m1(struct wpa_sm *sm, + struct wpa_tdls_peer *peer) +{ + size_t buf_len; + struct wpa_tdls_timeoutie timeoutie; + u16 rsn_capab; + struct wpa_tdls_ftie *ftie; + u8 *rbuf, *pos, *count_pos; + u16 count; + struct rsn_ie_hdr *hdr; + int status; + + if (!wpa_tdls_get_privacy(sm)) { + wpa_printf(MSG_DEBUG, "TDLS: No security used on the link"); + peer->rsnie_i_len = 0; + goto skip_rsnie; + } + + /* + * TPK Handshake Message 1: + * FTIE: ANonce=0, SNonce=initiator nonce MIC=0, DataKDs=(RSNIE_I, + * Timeout Interval IE)) + */ + + /* Filling RSN IE */ + hdr = (struct rsn_ie_hdr *) peer->rsnie_i; + hdr->elem_id = WLAN_EID_RSN; + WPA_PUT_LE16(hdr->version, RSN_VERSION); + + pos = (u8 *) (hdr + 1); + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + pos += RSN_SELECTOR_LEN; + count_pos = pos; + pos += 2; + + count = 0; + + /* + * AES-CCMP is the default Encryption preferred for TDLS, so + * RSN IE is filled only with CCMP CIPHER + * Note: TKIP is not used to encrypt TDLS link. + * + * Regardless of the cipher used on the AP connection, select CCMP + * here. + */ + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_CCMP); + pos += RSN_SELECTOR_LEN; + count++; + + WPA_PUT_LE16(count_pos, count); + + WPA_PUT_LE16(pos, 1); + pos += 2; + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_TPK_HANDSHAKE); + pos += RSN_SELECTOR_LEN; + + rsn_capab = WPA_CAPABILITY_PEERKEY_ENABLED; + rsn_capab |= RSN_NUM_REPLAY_COUNTERS_16 << 2; +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_ALT_RSN_IE) { + wpa_printf(MSG_DEBUG, "TDLS: Use alternative RSN IE for " + "testing"); + rsn_capab = WPA_CAPABILITY_PEERKEY_ENABLED; + } +#endif /* CONFIG_TDLS_TESTING */ + WPA_PUT_LE16(pos, rsn_capab); + pos += 2; +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_ALT_RSN_IE) { + /* Number of PMKIDs */ + *pos++ = 0x00; + *pos++ = 0x00; + } +#endif /* CONFIG_TDLS_TESTING */ + + hdr->len = (pos - peer->rsnie_i) - 2; + peer->rsnie_i_len = pos - peer->rsnie_i; + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE for TPK handshake", + peer->rsnie_i, peer->rsnie_i_len); + +skip_rsnie: + buf_len = 0; + if (wpa_tdls_get_privacy(sm)) + buf_len += peer->rsnie_i_len + sizeof(struct wpa_tdls_ftie) + + sizeof(struct wpa_tdls_timeoutie); +#ifdef CONFIG_TDLS_TESTING + if (wpa_tdls_get_privacy(sm) && + (tdls_testing & TDLS_TESTING_LONG_FRAME)) + buf_len += 170; + if (tdls_testing & TDLS_TESTING_DIFF_BSSID) + buf_len += sizeof(struct wpa_tdls_lnkid); +#endif /* CONFIG_TDLS_TESTING */ + rbuf = os_zalloc(buf_len + 1); + if (rbuf == NULL) { + wpa_tdls_peer_free(sm, peer); + return -1; + } + pos = rbuf; + + if (!wpa_tdls_get_privacy(sm)) + goto skip_ies; + + /* Initiator RSN IE */ + pos = wpa_add_ie(pos, peer->rsnie_i, peer->rsnie_i_len); + + ftie = (struct wpa_tdls_ftie *) pos; + ftie->ie_type = WLAN_EID_FAST_BSS_TRANSITION; + ftie->ie_len = sizeof(struct wpa_tdls_ftie) - 2; + + if (os_get_random(peer->inonce, WPA_NONCE_LEN)) { + wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, + "TDLS: Failed to get random data for initiator Nonce"); + os_free(rbuf); + wpa_tdls_peer_free(sm, peer); + return -1; + } + wpa_hexdump(MSG_DEBUG, "TDLS: Initiator Nonce for TPK handshake", + peer->inonce, WPA_NONCE_LEN); + os_memcpy(ftie->Snonce, peer->inonce, WPA_NONCE_LEN); + + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE for TPK Handshake M1", + (u8 *) ftie, sizeof(struct wpa_tdls_ftie)); + + pos = (u8 *) (ftie + 1); + +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_LONG_FRAME) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - add extra subelem to " + "FTIE"); + ftie->ie_len += 170; + *pos++ = 255; /* FTIE subelem */ + *pos++ = 168; /* FTIE subelem length */ + pos += 168; + } +#endif /* CONFIG_TDLS_TESTING */ + + /* Lifetime */ + peer->lifetime = TPK_LIFETIME; +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_SHORT_LIFETIME) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use short TPK " + "lifetime"); + peer->lifetime = 301; + } + if (tdls_testing & TDLS_TESTING_LONG_LIFETIME) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use long TPK " + "lifetime"); + peer->lifetime = 0xffffffff; + } +#endif /* CONFIG_TDLS_TESTING */ + pos = wpa_add_tdls_timeoutie(pos, (u8 *) &timeoutie, + sizeof(timeoutie), peer->lifetime); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds", peer->lifetime); + +skip_ies: + +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_DIFF_BSSID) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use incorrect BSSID in " + "Link Identifier"); + struct wpa_tdls_lnkid *l = (struct wpa_tdls_lnkid *) pos; + wpa_tdls_linkid(sm, peer, l); + l->bssid[5] ^= 0x01; + pos += sizeof(*l); + } +#endif /* CONFIG_TDLS_TESTING */ + + wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Setup Request / TPK " + "Handshake Message 1 (peer " MACSTR ")", + MAC2STR(peer->addr)); + + status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST, + 1, 0, 0, peer->initiator, rbuf, pos - rbuf); + os_free(rbuf); + + return status; +} + + +static int wpa_tdls_send_tpk_m2(struct wpa_sm *sm, + const unsigned char *src_addr, u8 dtoken, + struct wpa_tdls_lnkid *lnkid, + const struct wpa_tdls_peer *peer) +{ + u8 *rbuf, *pos; + size_t buf_len; + u32 lifetime; + struct wpa_tdls_timeoutie timeoutie; + struct wpa_tdls_ftie *ftie; + int status; + + buf_len = 0; + if (wpa_tdls_get_privacy(sm)) { + /* Peer RSN IE, FTIE(Initiator Nonce, Responder Nonce), + * Lifetime */ + buf_len += peer->rsnie_i_len + sizeof(struct wpa_tdls_ftie) + + sizeof(struct wpa_tdls_timeoutie); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_LONG_FRAME) + buf_len += 170; +#endif /* CONFIG_TDLS_TESTING */ + } + + rbuf = os_zalloc(buf_len + 1); + if (rbuf == NULL) + return -1; + pos = rbuf; + + if (!wpa_tdls_get_privacy(sm)) + goto skip_ies; + + /* Peer RSN IE */ + pos = wpa_add_ie(pos, peer->rsnie_p, peer->rsnie_p_len); + + ftie = (struct wpa_tdls_ftie *) pos; + ftie->ie_type = WLAN_EID_FAST_BSS_TRANSITION; + /* TODO: ftie->mic_control to set 2-RESPONSE */ + os_memcpy(ftie->Anonce, peer->rnonce, WPA_NONCE_LEN); + os_memcpy(ftie->Snonce, peer->inonce, WPA_NONCE_LEN); + ftie->ie_len = sizeof(struct wpa_tdls_ftie) - 2; + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE for TPK M2", + (u8 *) ftie, sizeof(*ftie)); + + pos = (u8 *) (ftie + 1); + +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_LONG_FRAME) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - add extra subelem to " + "FTIE"); + ftie->ie_len += 170; + *pos++ = 255; /* FTIE subelem */ + *pos++ = 168; /* FTIE subelem length */ + pos += 168; + } +#endif /* CONFIG_TDLS_TESTING */ + + /* Lifetime */ + lifetime = peer->lifetime; +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_LIFETIME_RESP) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong TPK " + "lifetime in response"); + lifetime++; + } +#endif /* CONFIG_TDLS_TESTING */ + pos = wpa_add_tdls_timeoutie(pos, (u8 *) &timeoutie, + sizeof(timeoutie), lifetime); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds from initiator", + lifetime); + + /* compute MIC before sending */ + wpa_tdls_ftie_mic(peer->tpk.kck, 2, (u8 *) lnkid, peer->rsnie_p, + (u8 *) &timeoutie, (u8 *) ftie, ftie->mic); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_MIC) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong MIC"); + ftie->mic[0] ^= 0x01; + } +#endif /* CONFIG_TDLS_TESTING */ + +skip_ies: + status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, + dtoken, 0, 0, peer->initiator, rbuf, + pos - rbuf); + os_free(rbuf); + + return status; +} + + +static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm, + const unsigned char *src_addr, u8 dtoken, + struct wpa_tdls_lnkid *lnkid, + const struct wpa_tdls_peer *peer) +{ + u8 *rbuf, *pos; + size_t buf_len; + struct wpa_tdls_ftie *ftie; + struct wpa_tdls_timeoutie timeoutie; + u32 lifetime; + int status; + u32 peer_capab = 0; + + buf_len = 0; + if (wpa_tdls_get_privacy(sm)) { + /* Peer RSN IE, FTIE(Initiator Nonce, Responder Nonce), + * Lifetime */ + buf_len += peer->rsnie_i_len + sizeof(struct wpa_tdls_ftie) + + sizeof(struct wpa_tdls_timeoutie); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_LONG_FRAME) + buf_len += 170; +#endif /* CONFIG_TDLS_TESTING */ + } + + rbuf = os_zalloc(buf_len + 1); + if (rbuf == NULL) + return -1; + pos = rbuf; + + if (!wpa_tdls_get_privacy(sm)) + goto skip_ies; + + /* Peer RSN IE */ + pos = wpa_add_ie(pos, peer->rsnie_p, peer->rsnie_p_len); + + ftie = (struct wpa_tdls_ftie *) pos; + ftie->ie_type = WLAN_EID_FAST_BSS_TRANSITION; + /*TODO: ftie->mic_control to set 3-CONFIRM */ + os_memcpy(ftie->Anonce, peer->rnonce, WPA_NONCE_LEN); + os_memcpy(ftie->Snonce, peer->inonce, WPA_NONCE_LEN); + ftie->ie_len = sizeof(struct wpa_tdls_ftie) - 2; + + pos = (u8 *) (ftie + 1); + +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_LONG_FRAME) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - add extra subelem to " + "FTIE"); + ftie->ie_len += 170; + *pos++ = 255; /* FTIE subelem */ + *pos++ = 168; /* FTIE subelem length */ + pos += 168; + } +#endif /* CONFIG_TDLS_TESTING */ + + /* Lifetime */ + lifetime = peer->lifetime; +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_LIFETIME_CONF) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong TPK " + "lifetime in confirm"); + lifetime++; + } +#endif /* CONFIG_TDLS_TESTING */ + pos = wpa_add_tdls_timeoutie(pos, (u8 *) &timeoutie, + sizeof(timeoutie), lifetime); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds", + lifetime); + + /* compute MIC before sending */ + wpa_tdls_ftie_mic(peer->tpk.kck, 3, (u8 *) lnkid, peer->rsnie_p, + (u8 *) &timeoutie, (u8 *) ftie, ftie->mic); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_MIC) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong MIC"); + ftie->mic[0] ^= 0x01; + } +#endif /* CONFIG_TDLS_TESTING */ + +skip_ies: + + if (peer->vht_capabilities) + peer_capab |= TDLS_PEER_VHT; + if (peer->ht_capabilities) + peer_capab |= TDLS_PEER_HT; + if (peer->wmm_capable) + peer_capab |= TDLS_PEER_WMM; + + status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, + dtoken, 0, peer_capab, peer->initiator, + rbuf, pos - rbuf); + os_free(rbuf); + + return status; +} + + +static int wpa_tdls_send_discovery_response(struct wpa_sm *sm, + struct wpa_tdls_peer *peer, + u8 dialog_token) +{ + size_t buf_len = 0; + struct wpa_tdls_timeoutie timeoutie; + u16 rsn_capab; + u8 *rbuf, *pos, *count_pos; + u16 count; + struct rsn_ie_hdr *hdr; + int status; + + wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Discovery Response " + "(peer " MACSTR ")", MAC2STR(peer->addr)); + if (!wpa_tdls_get_privacy(sm)) + goto skip_rsn_ies; + + /* Filling RSN IE */ + hdr = (struct rsn_ie_hdr *) peer->rsnie_i; + hdr->elem_id = WLAN_EID_RSN; + WPA_PUT_LE16(hdr->version, RSN_VERSION); + pos = (u8 *) (hdr + 1); + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + pos += RSN_SELECTOR_LEN; + count_pos = pos; + pos += 2; + count = 0; + + /* + * AES-CCMP is the default encryption preferred for TDLS, so + * RSN IE is filled only with CCMP cipher suite. + * Note: TKIP is not used to encrypt TDLS link. + * + * Regardless of the cipher used on the AP connection, select CCMP + * here. + */ + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_CCMP); + pos += RSN_SELECTOR_LEN; + count++; + WPA_PUT_LE16(count_pos, count); + WPA_PUT_LE16(pos, 1); + pos += 2; + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_TPK_HANDSHAKE); + pos += RSN_SELECTOR_LEN; + + rsn_capab = WPA_CAPABILITY_PEERKEY_ENABLED; + rsn_capab |= RSN_NUM_REPLAY_COUNTERS_16 << 2; + WPA_PUT_LE16(pos, rsn_capab); + pos += 2; + hdr->len = (pos - (u8 *) hdr) - 2; + peer->rsnie_i_len = pos - peer->rsnie_i; + + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE for Discovery Response", + (u8 *) hdr, hdr->len + 2); +skip_rsn_ies: + buf_len = 0; + if (wpa_tdls_get_privacy(sm)) { + /* Peer RSN IE, Lifetime */ + buf_len += peer->rsnie_i_len + + sizeof(struct wpa_tdls_timeoutie); + } + rbuf = os_zalloc(buf_len + 1); + if (rbuf == NULL) { + wpa_tdls_peer_free(sm, peer); + return -1; + } + pos = rbuf; + + if (!wpa_tdls_get_privacy(sm)) + goto skip_ies; + /* Initiator RSN IE */ + pos = wpa_add_ie(pos, peer->rsnie_i, peer->rsnie_i_len); + /* Lifetime */ + peer->lifetime = TPK_LIFETIME; + pos = wpa_add_tdls_timeoutie(pos, (u8 *) &timeoutie, + sizeof(timeoutie), peer->lifetime); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds", peer->lifetime); +skip_ies: + status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE, + dialog_token, 0, 0, 0, rbuf, pos - rbuf); + os_free(rbuf); + + return status; +} + + +static int +wpa_tdls_process_discovery_request(struct wpa_sm *sm, const u8 *addr, + const u8 *buf, size_t len) +{ + struct wpa_eapol_ie_parse kde; + const struct wpa_tdls_lnkid *lnkid; + struct wpa_tdls_peer *peer; + size_t min_req_len = sizeof(struct wpa_tdls_frame) + + 1 /* dialog token */ + sizeof(struct wpa_tdls_lnkid); + u8 dialog_token; + + wpa_printf(MSG_DEBUG, "TDLS: Discovery Request from " MACSTR, + MAC2STR(addr)); + + if (len < min_req_len) { + wpa_printf(MSG_DEBUG, "TDLS Discovery Request is too short: " + "%d", (int) len); + return -1; + } + + dialog_token = buf[sizeof(struct wpa_tdls_frame)]; + + /* + * Some APs will tack on a weird IE to the end of a TDLS + * discovery request packet. This needn't fail the response, + * since the required IE are verified separately. + */ + if (wpa_supplicant_parse_ies(buf + sizeof(struct wpa_tdls_frame) + 1, + len - (sizeof(struct wpa_tdls_frame) + 1), + &kde) < 0) { + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in Discovery Request - ignore as an interop workaround"); + } + + if (!kde.lnkid) { + wpa_printf(MSG_DEBUG, "TDLS: Link ID not found in Discovery " + "Request"); + return -1; + } + + lnkid = (const struct wpa_tdls_lnkid *) kde.lnkid; + + if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "TDLS: Discovery Request from different " + " BSS " MACSTR, MAC2STR(lnkid->bssid)); + return -1; + } + + peer = wpa_tdls_add_peer(sm, addr, NULL); + if (peer == NULL) + return -1; + + return wpa_tdls_send_discovery_response(sm, peer, dialog_token); +} + + +int wpa_tdls_send_discovery_request(struct wpa_sm *sm, const u8 *addr) +{ + if (sm->tdls_disabled || !sm->tdls_supported) + return -1; + + wpa_printf(MSG_DEBUG, "TDLS: Sending Discovery Request to peer " + MACSTR, MAC2STR(addr)); + return wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_DISCOVERY_REQUEST, + 1, 0, 0, 1, NULL, 0); +} + + +static int copy_supp_rates(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->supp_rates) { + wpa_printf(MSG_DEBUG, "TDLS: No supported rates received"); + return -1; + } + peer->supp_rates_len = merge_byte_arrays( + peer->supp_rates, sizeof(peer->supp_rates), + kde->supp_rates + 2, kde->supp_rates_len - 2, + kde->ext_supp_rates ? kde->ext_supp_rates + 2 : NULL, + kde->ext_supp_rates_len - 2); + return 0; +} + + +static int copy_peer_ht_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->ht_capabilities) { + wpa_printf(MSG_DEBUG, "TDLS: No supported ht capabilities " + "received"); + return 0; + } + + if (!peer->ht_capabilities) { + peer->ht_capabilities = + os_zalloc(sizeof(struct ieee80211_ht_capabilities)); + if (peer->ht_capabilities == NULL) + return -1; + } + + os_memcpy(peer->ht_capabilities, kde->ht_capabilities, + sizeof(struct ieee80211_ht_capabilities)); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer HT capabilities", + (u8 *) peer->ht_capabilities, + sizeof(struct ieee80211_ht_capabilities)); + + return 0; +} + + +static int copy_peer_vht_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->vht_capabilities) { + wpa_printf(MSG_DEBUG, "TDLS: No supported vht capabilities " + "received"); + return 0; + } + + if (!peer->vht_capabilities) { + peer->vht_capabilities = + os_zalloc(sizeof(struct ieee80211_vht_capabilities)); + if (peer->vht_capabilities == NULL) + return -1; + } + + os_memcpy(peer->vht_capabilities, kde->vht_capabilities, + sizeof(struct ieee80211_vht_capabilities)); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer VHT capabilities", + (u8 *) peer->vht_capabilities, + sizeof(struct ieee80211_vht_capabilities)); + + return 0; +} + + +static int copy_peer_ext_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->ext_capab) { + wpa_printf(MSG_DEBUG, "TDLS: No extended capabilities " + "received"); + return 0; + } + + if (!peer->ext_capab || peer->ext_capab_len < kde->ext_capab_len - 2) { + /* Need to allocate buffer to fit the new information */ + os_free(peer->ext_capab); + peer->ext_capab = os_zalloc(kde->ext_capab_len - 2); + if (peer->ext_capab == NULL) + return -1; + } + + peer->ext_capab_len = kde->ext_capab_len - 2; + os_memcpy(peer->ext_capab, kde->ext_capab + 2, peer->ext_capab_len); + + return 0; +} + + +static int copy_peer_wmm_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + struct wmm_information_element *wmm; + + if (!kde->wmm) { + wpa_printf(MSG_DEBUG, "TDLS: No supported WMM capabilities received"); + return 0; + } + + if (kde->wmm_len < sizeof(struct wmm_information_element)) { + wpa_printf(MSG_DEBUG, "TDLS: Invalid supported WMM capabilities received"); + return -1; + } + + wmm = (struct wmm_information_element *) kde->wmm; + peer->qos_info = wmm->qos_info; + + peer->wmm_capable = 1; + + wpa_printf(MSG_DEBUG, "TDLS: Peer WMM QOS Info 0x%x", peer->qos_info); + return 0; +} + + +static int copy_peer_supp_channels(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->supp_channels) { + wpa_printf(MSG_DEBUG, "TDLS: No supported channels received"); + return 0; + } + + if (!peer->supp_channels || + peer->supp_channels_len < kde->supp_channels_len) { + os_free(peer->supp_channels); + peer->supp_channels = os_zalloc(kde->supp_channels_len); + if (peer->supp_channels == NULL) + return -1; + } + + peer->supp_channels_len = kde->supp_channels_len; + + os_memcpy(peer->supp_channels, kde->supp_channels, + peer->supp_channels_len); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer Supported Channels", + (u8 *) peer->supp_channels, peer->supp_channels_len); + return 0; +} + + +static int copy_peer_supp_oper_classes(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->supp_oper_classes) { + wpa_printf(MSG_DEBUG, "TDLS: No supported operating classes received"); + return 0; + } + + if (!peer->supp_oper_classes || + peer->supp_oper_classes_len < kde->supp_oper_classes_len) { + os_free(peer->supp_oper_classes); + peer->supp_oper_classes = os_zalloc(kde->supp_oper_classes_len); + if (peer->supp_oper_classes == NULL) + return -1; + } + + peer->supp_oper_classes_len = kde->supp_oper_classes_len; + os_memcpy(peer->supp_oper_classes, kde->supp_oper_classes, + peer->supp_oper_classes_len); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer Supported Operating Classes", + (u8 *) peer->supp_oper_classes, + peer->supp_oper_classes_len); + return 0; +} + + +static int wpa_tdls_addset_peer(struct wpa_sm *sm, struct wpa_tdls_peer *peer, + int add) +{ + return wpa_sm_tdls_peer_addset(sm, peer->addr, add, peer->aid, + peer->capability, + peer->supp_rates, peer->supp_rates_len, + peer->ht_capabilities, + peer->vht_capabilities, + peer->qos_info, peer->wmm_capable, + peer->ext_capab, peer->ext_capab_len, + peer->supp_channels, + peer->supp_channels_len, + peer->supp_oper_classes, + peer->supp_oper_classes_len); +} + + +static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, + const u8 *buf, size_t len) +{ + struct wpa_tdls_peer *peer; + struct wpa_eapol_ie_parse kde; + struct wpa_ie_data ie; + int cipher; + const u8 *cpos; + struct wpa_tdls_ftie *ftie = NULL; + struct wpa_tdls_timeoutie *timeoutie; + struct wpa_tdls_lnkid *lnkid; + u32 lifetime = 0; +#if 0 + struct rsn_ie_hdr *hdr; + u8 *pos; + u16 rsn_capab; + u16 rsn_ver; +#endif + u8 dtoken; + u16 ielen; + u16 status = WLAN_STATUS_UNSPECIFIED_FAILURE; + int tdls_prohibited = sm->tdls_prohibited; + int existing_peer = 0; + + if (len < 3 + 3) + return -1; + + cpos = buf; + cpos += 1 /* pkt_type */ + 1 /* Category */ + 1 /* Action */; + + /* driver had already verified the frame format */ + dtoken = *cpos++; /* dialog token */ + + wpa_printf(MSG_INFO, "TDLS: Dialog Token in TPK M1 %d", dtoken); + + peer = wpa_tdls_add_peer(sm, src_addr, &existing_peer); + if (peer == NULL) + goto error; + + /* If found, use existing entry instead of adding a new one; + * how to handle the case where both ends initiate at the + * same time? */ + if (existing_peer) { + if (peer->tpk_success) { + wpa_printf(MSG_DEBUG, "TDLS: TDLS Setup Request while " + "direct link is enabled - tear down the " + "old link first"); + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); + wpa_tdls_peer_clear(sm, peer); + } else if (peer->initiator) { + /* + * An entry is already present, so check if we already + * sent a TDLS Setup Request. If so, compare MAC + * addresses and let the STA with the lower MAC address + * continue as the initiator. The other negotiation is + * terminated. + */ + if (os_memcmp(sm->own_addr, src_addr, ETH_ALEN) < 0) { + wpa_printf(MSG_DEBUG, "TDLS: Discard request " + "from peer with higher address " + MACSTR, MAC2STR(src_addr)); + return -1; + } else { + wpa_printf(MSG_DEBUG, "TDLS: Accept request " + "from peer with lower address " + MACSTR " (terminate previously " + "initiated negotiation", + MAC2STR(src_addr)); + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, + peer->addr); + wpa_tdls_peer_clear(sm, peer); + } + } + } + + /* capability information */ + peer->capability = WPA_GET_LE16(cpos); + cpos += 2; + + ielen = len - (cpos - buf); /* start of IE in buf */ + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs may add arbitrary padding to the + * end of short TDLS frames and that would look like invalid IEs. + */ + if (wpa_supplicant_parse_ies(cpos, ielen, &kde) < 0) + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in TPK M1 - ignore as an interop workaround"); + + if (kde.lnkid == NULL || kde.lnkid_len < 3 * ETH_ALEN) { + wpa_printf(MSG_INFO, "TDLS: No valid Link Identifier IE in " + "TPK M1"); + goto error; + } + wpa_hexdump(MSG_DEBUG, "TDLS: Link ID Received from TPK M1", + kde.lnkid, kde.lnkid_len); + lnkid = (struct wpa_tdls_lnkid *) kde.lnkid; + if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) { + wpa_printf(MSG_INFO, "TDLS: TPK M1 from diff BSS"); + status = WLAN_STATUS_REQUEST_DECLINED; + goto error; + } + + wpa_printf(MSG_DEBUG, "TDLS: TPK M1 - TPK initiator " MACSTR, + MAC2STR(src_addr)); + + if (copy_supp_rates(&kde, peer) < 0) + goto error; + + if (copy_peer_ht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_vht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_ext_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_channels(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_oper_classes(&kde, peer) < 0) + goto error; + + peer->qos_info = kde.qosinfo; + + /* Overwrite with the qos_info obtained in WMM IE */ + if (copy_peer_wmm_capab(&kde, peer) < 0) + goto error; + + peer->aid = kde.aid; + +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_CONCURRENT_INIT) { + peer = wpa_tdls_add_peer(sm, src_addr, NULL); + if (peer == NULL) + goto error; + wpa_printf(MSG_DEBUG, "TDLS: Testing concurrent initiation of " + "TDLS setup - send own request"); + peer->initiator = 1; + wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, 0, NULL, 0, NULL, + NULL, 0, 0, NULL, 0, NULL, 0, NULL, 0); + wpa_tdls_send_tpk_m1(sm, peer); + } + + if ((tdls_testing & TDLS_TESTING_IGNORE_AP_PROHIBIT) && + tdls_prohibited) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - ignore AP prohibition " + "on TDLS"); + tdls_prohibited = 0; + } +#endif /* CONFIG_TDLS_TESTING */ + + if (tdls_prohibited) { + wpa_printf(MSG_INFO, "TDLS: TDLS prohibited in this BSS"); + status = WLAN_STATUS_REQUEST_DECLINED; + goto error; + } + + if (!wpa_tdls_get_privacy(sm)) { + if (kde.rsn_ie) { + wpa_printf(MSG_INFO, "TDLS: RSN IE in TPK M1 while " + "security is disabled"); + status = WLAN_STATUS_SECURITY_DISABLED; + goto error; + } + goto skip_rsn; + } + + if (kde.ftie == NULL || kde.ftie_len < sizeof(*ftie) || + kde.rsn_ie == NULL) { + wpa_printf(MSG_INFO, "TDLS: No FTIE or RSN IE in TPK M1"); + status = WLAN_STATUS_INVALID_PARAMETERS; + goto error; + } + + if (kde.rsn_ie_len > TDLS_MAX_IE_LEN) { + wpa_printf(MSG_INFO, "TDLS: Too long Initiator RSN IE in " + "TPK M1"); + status = WLAN_STATUS_INVALID_RSNIE; + goto error; + } + + if (wpa_parse_wpa_ie_rsn(kde.rsn_ie, kde.rsn_ie_len, &ie) < 0) { + wpa_printf(MSG_INFO, "TDLS: Failed to parse RSN IE in TPK M1"); + status = WLAN_STATUS_INVALID_RSNIE; + goto error; + } + + cipher = ie.pairwise_cipher; + if (cipher & WPA_CIPHER_CCMP) { + wpa_printf(MSG_DEBUG, "TDLS: Using CCMP for direct link"); + cipher = WPA_CIPHER_CCMP; + } else { + wpa_printf(MSG_INFO, "TDLS: No acceptable cipher in TPK M1"); + status = WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID; + goto error; + } + + if ((ie.capabilities & + (WPA_CAPABILITY_NO_PAIRWISE | WPA_CAPABILITY_PEERKEY_ENABLED)) != + WPA_CAPABILITY_PEERKEY_ENABLED) { + wpa_printf(MSG_INFO, "TDLS: Invalid RSN Capabilities in " + "TPK M1"); + status = WLAN_STATUS_INVALID_RSN_IE_CAPAB; + goto error; + } + + /* Lifetime */ + if (kde.key_lifetime == NULL) { + wpa_printf(MSG_INFO, "TDLS: No Key Lifetime IE in TPK M1"); + status = WLAN_STATUS_UNACCEPTABLE_LIFETIME; + goto error; + } + timeoutie = (struct wpa_tdls_timeoutie *) kde.key_lifetime; + lifetime = WPA_GET_LE32(timeoutie->value); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds", lifetime); + if (lifetime < 300) { + wpa_printf(MSG_INFO, "TDLS: Too short TPK lifetime"); + status = WLAN_STATUS_UNACCEPTABLE_LIFETIME; + goto error; + } + +skip_rsn: +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_CONCURRENT_INIT) { + if (os_memcmp(sm->own_addr, peer->addr, ETH_ALEN) < 0) { + /* + * The request frame from us is going to win, so do not + * replace information based on this request frame from + * the peer. + */ + goto skip_rsn_check; + } + } +#endif /* CONFIG_TDLS_TESTING */ + + peer->initiator = 0; /* Need to check */ + peer->dtoken = dtoken; + + if (!wpa_tdls_get_privacy(sm)) { + peer->rsnie_i_len = 0; + peer->rsnie_p_len = 0; + peer->cipher = WPA_CIPHER_NONE; + goto skip_rsn_check; + } + + ftie = (struct wpa_tdls_ftie *) kde.ftie; + os_memcpy(peer->rsnie_i, kde.rsn_ie, kde.rsn_ie_len); + peer->rsnie_i_len = kde.rsn_ie_len; + peer->cipher = cipher; + + if (os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) != 0) { + /* + * There is no point in updating the RNonce for every obtained + * TPK M1 frame (e.g., retransmission due to timeout) with the + * same INonce (SNonce in FTIE). However, if the TPK M1 is + * retransmitted with a different INonce, update the RNonce + * since this is for a new TDLS session. + */ + wpa_printf(MSG_DEBUG, + "TDLS: New TPK M1 INonce - generate new RNonce"); + os_memcpy(peer->inonce, ftie->Snonce, WPA_NONCE_LEN); + if (os_get_random(peer->rnonce, WPA_NONCE_LEN)) { + wpa_msg(sm->ctx->ctx, MSG_WARNING, + "TDLS: Failed to get random data for responder nonce"); + goto error; + } + } + +#if 0 + /* get version info from RSNIE received from Peer */ + hdr = (struct rsn_ie_hdr *) kde.rsn_ie; + rsn_ver = WPA_GET_LE16(hdr->version); + + /* use min(peer's version, out version) */ + if (rsn_ver > RSN_VERSION) + rsn_ver = RSN_VERSION; + + hdr = (struct rsn_ie_hdr *) peer->rsnie_p; + + hdr->elem_id = WLAN_EID_RSN; + WPA_PUT_LE16(hdr->version, rsn_ver); + pos = (u8 *) (hdr + 1); + + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + pos += RSN_SELECTOR_LEN; + /* Include only the selected cipher in pairwise cipher suite */ + WPA_PUT_LE16(pos, 1); + pos += 2; + if (cipher == WPA_CIPHER_CCMP) + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_CCMP); + pos += RSN_SELECTOR_LEN; + + WPA_PUT_LE16(pos, 1); + pos += 2; + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_TPK_HANDSHAKE); + pos += RSN_SELECTOR_LEN; + + rsn_capab = WPA_CAPABILITY_PEERKEY_ENABLED; + rsn_capab |= RSN_NUM_REPLAY_COUNTERS_16 << 2; + WPA_PUT_LE16(pos, rsn_capab); + pos += 2; + + hdr->len = (pos - peer->rsnie_p) - 2; + peer->rsnie_p_len = pos - peer->rsnie_p; +#endif + + /* temp fix: validation of RSNIE later */ + os_memcpy(peer->rsnie_p, peer->rsnie_i, peer->rsnie_i_len); + peer->rsnie_p_len = peer->rsnie_i_len; + + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE for TPK handshake", + peer->rsnie_p, peer->rsnie_p_len); + + peer->lifetime = lifetime; + + wpa_tdls_generate_tpk(peer, sm->own_addr, sm->bssid); + +skip_rsn_check: +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_CONCURRENT_INIT) + goto skip_add_peer; +#endif /* CONFIG_TDLS_TESTING */ + + /* add supported rates, capabilities, and qos_info to the TDLS peer */ + if (wpa_tdls_addset_peer(sm, peer, 1) < 0) + goto error; + +#ifdef CONFIG_TDLS_TESTING +skip_add_peer: +#endif /* CONFIG_TDLS_TESTING */ + peer->tpk_in_progress = 1; + + wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Setup Response / TPK M2"); + if (wpa_tdls_send_tpk_m2(sm, src_addr, dtoken, lnkid, peer) < 0) { + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); + goto error; + } + + return 0; + +error: + wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, dtoken, 0, + status); + if (peer) + wpa_tdls_peer_free(sm, peer); + return -1; +} + + +static int wpa_tdls_enable_link(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +{ + peer->tpk_success = 1; + peer->tpk_in_progress = 0; + eloop_cancel_timeout(wpa_tdls_tpk_timeout, sm, peer); + if (wpa_tdls_get_privacy(sm)) { + u32 lifetime = peer->lifetime; + /* + * Start the initiator process a bit earlier to avoid race + * condition with the responder sending teardown request. + */ + if (lifetime > 3 && peer->initiator) + lifetime -= 3; + eloop_register_timeout(lifetime, 0, wpa_tdls_tpk_timeout, + sm, peer); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_NO_TPK_EXPIRATION) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - disable TPK " + "expiration"); + eloop_cancel_timeout(wpa_tdls_tpk_timeout, sm, peer); + } +#endif /* CONFIG_TDLS_TESTING */ + } + + if (peer->reconfig_key && wpa_tdls_set_key(sm, peer) < 0) { + wpa_printf(MSG_INFO, "TDLS: Could not configure key to the " + "driver"); + return -1; + } + peer->reconfig_key = 0; + + return wpa_sm_tdls_oper(sm, TDLS_ENABLE_LINK, peer->addr); +} + + +static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, + const u8 *buf, size_t len) +{ + struct wpa_tdls_peer *peer; + struct wpa_eapol_ie_parse kde; + struct wpa_ie_data ie; + int cipher; + struct wpa_tdls_ftie *ftie; + struct wpa_tdls_timeoutie *timeoutie; + struct wpa_tdls_lnkid *lnkid; + u32 lifetime; + u8 dtoken; + int ielen; + u16 status; + const u8 *pos; + int ret = 0; + + wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Response / TPK M2 " + "(Peer " MACSTR ")", MAC2STR(src_addr)); + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0) + break; + } + if (peer == NULL) { + wpa_printf(MSG_INFO, "TDLS: No matching peer found for " + "TPK M2: " MACSTR, MAC2STR(src_addr)); + return -1; + } + if (!peer->initiator) { + /* + * This may happen if both devices try to initiate TDLS at the + * same time and we accept the TPK M1 from the peer in + * wpa_tdls_process_tpk_m1() and clear our previous state. + */ + wpa_printf(MSG_INFO, "TDLS: We were not the initiator, so " + "ignore TPK M2 from " MACSTR, MAC2STR(src_addr)); + return -1; + } + wpa_tdls_tpk_retry_timeout_cancel(sm, peer, WLAN_TDLS_SETUP_REQUEST); + + if (len < 3 + 2 + 1) { + wpa_tdls_disable_peer_link(sm, peer); + return -1; + } + + pos = buf; + pos += 1 /* pkt_type */ + 1 /* Category */ + 1 /* Action */; + status = WPA_GET_LE16(pos); + pos += 2 /* status code */; + + if (status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_INFO, "TDLS: Status code in TPK M2: %u", + status); + wpa_tdls_disable_peer_link(sm, peer); + return -1; + } + + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + + /* TODO: need to verify dialog token matches here or in kernel */ + dtoken = *pos++; /* dialog token */ + + wpa_printf(MSG_DEBUG, "TDLS: Dialog Token in TPK M2 %d", dtoken); + + if (len < 3 + 2 + 1 + 2) { + wpa_tdls_disable_peer_link(sm, peer); + return -1; + } + + /* capability information */ + peer->capability = WPA_GET_LE16(pos); + pos += 2; + + ielen = len - (pos - buf); /* start of IE in buf */ + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs may add arbitrary padding to the + * end of short TDLS frames and that would look like invalid IEs. + */ + if (wpa_supplicant_parse_ies(pos, ielen, &kde) < 0) + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in TPK M2 - ignore as an interop workaround"); + +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_DECLINE_RESP) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - decline response"); + status = WLAN_STATUS_REQUEST_DECLINED; + goto error; + } +#endif /* CONFIG_TDLS_TESTING */ + + if (kde.lnkid == NULL || kde.lnkid_len < 3 * ETH_ALEN) { + wpa_printf(MSG_INFO, "TDLS: No valid Link Identifier IE in " + "TPK M2"); + goto error; + } + wpa_hexdump(MSG_DEBUG, "TDLS: Link ID Received from TPK M2", + kde.lnkid, kde.lnkid_len); + lnkid = (struct wpa_tdls_lnkid *) kde.lnkid; + + if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) { + wpa_printf(MSG_INFO, "TDLS: TPK M2 from different BSS"); + status = WLAN_STATUS_NOT_IN_SAME_BSS; + goto error; + } + + if (copy_supp_rates(&kde, peer) < 0) + goto error; + + if (copy_peer_ht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_vht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_ext_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_channels(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_oper_classes(&kde, peer) < 0) + goto error; + + peer->qos_info = kde.qosinfo; + + /* Overwrite with the qos_info obtained in WMM IE */ + if (copy_peer_wmm_capab(&kde, peer) < 0) + goto error; + + peer->aid = kde.aid; + + if (!wpa_tdls_get_privacy(sm)) { + peer->rsnie_p_len = 0; + peer->cipher = WPA_CIPHER_NONE; + goto skip_rsn; + } + + if (kde.ftie == NULL || kde.ftie_len < sizeof(*ftie) || + kde.rsn_ie == NULL) { + wpa_printf(MSG_INFO, "TDLS: No FTIE or RSN IE in TPK M2"); + status = WLAN_STATUS_INVALID_PARAMETERS; + goto error; + } + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE Received from TPK M2", + kde.rsn_ie, kde.rsn_ie_len); + + if (kde.rsn_ie_len > TDLS_MAX_IE_LEN) { + wpa_printf(MSG_INFO, + "TDLS: Too long Responder RSN IE in TPK M2"); + status = WLAN_STATUS_INVALID_RSNIE; + goto error; + } + + /* + * FIX: bitwise comparison of RSN IE is not the correct way of + * validation this. It can be different, but certain fields must + * match. Since we list only a single pairwise cipher in TPK M1, the + * memcmp is likely to work in most cases, though. + */ + if (kde.rsn_ie_len != peer->rsnie_i_len || + os_memcmp(peer->rsnie_i, kde.rsn_ie, peer->rsnie_i_len) != 0) { + wpa_printf(MSG_INFO, "TDLS: RSN IE in TPK M2 does " + "not match with RSN IE used in TPK M1"); + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE Sent in TPK M1", + peer->rsnie_i, peer->rsnie_i_len); + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE Received from TPK M2", + kde.rsn_ie, kde.rsn_ie_len); + status = WLAN_STATUS_INVALID_RSNIE; + goto error; + } + + if (wpa_parse_wpa_ie_rsn(kde.rsn_ie, kde.rsn_ie_len, &ie) < 0) { + wpa_printf(MSG_INFO, "TDLS: Failed to parse RSN IE in TPK M2"); + status = WLAN_STATUS_INVALID_RSNIE; + goto error; + } + + cipher = ie.pairwise_cipher; + if (cipher == WPA_CIPHER_CCMP) { + wpa_printf(MSG_DEBUG, "TDLS: Using CCMP for direct link"); + cipher = WPA_CIPHER_CCMP; + } else { + wpa_printf(MSG_INFO, "TDLS: No acceptable cipher in TPK M2"); + status = WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID; + goto error; + } + + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE Received from TPK M2", + kde.ftie, sizeof(*ftie)); + ftie = (struct wpa_tdls_ftie *) kde.ftie; + + if (!os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) == 0) { + wpa_printf(MSG_INFO, "TDLS: FTIE SNonce in TPK M2 does " + "not match with FTIE SNonce used in TPK M1"); + /* Silently discard the frame */ + return -1; + } + + /* Responder Nonce and RSN IE */ + os_memcpy(peer->rnonce, ftie->Anonce, WPA_NONCE_LEN); + os_memcpy(peer->rsnie_p, kde.rsn_ie, kde.rsn_ie_len); + peer->rsnie_p_len = kde.rsn_ie_len; + peer->cipher = cipher; + + /* Lifetime */ + if (kde.key_lifetime == NULL) { + wpa_printf(MSG_INFO, "TDLS: No Key Lifetime IE in TPK M2"); + status = WLAN_STATUS_UNACCEPTABLE_LIFETIME; + goto error; + } + timeoutie = (struct wpa_tdls_timeoutie *) kde.key_lifetime; + lifetime = WPA_GET_LE32(timeoutie->value); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds in TPK M2", + lifetime); + if (lifetime != peer->lifetime) { + wpa_printf(MSG_INFO, "TDLS: Unexpected TPK lifetime %u in " + "TPK M2 (expected %u)", lifetime, peer->lifetime); + status = WLAN_STATUS_UNACCEPTABLE_LIFETIME; + goto error; + } + + wpa_tdls_generate_tpk(peer, sm->own_addr, sm->bssid); + + /* Process MIC check to see if TPK M2 is right */ + if (wpa_supplicant_verify_tdls_mic(2, peer, (u8 *) lnkid, + (u8 *) timeoutie, ftie) < 0) { + /* Discard the frame */ + wpa_tdls_del_key(sm, peer); + wpa_tdls_disable_peer_link(sm, peer); + return -1; + } + + if (wpa_tdls_set_key(sm, peer) < 0) { + /* + * Some drivers may not be able to config the key prior to full + * STA entry having been configured. + */ + wpa_printf(MSG_DEBUG, "TDLS: Try to configure TPK again after " + "STA entry is complete"); + peer->reconfig_key = 1; + } + +skip_rsn: + peer->dtoken = dtoken; + + /* add supported rates, capabilities, and qos_info to the TDLS peer */ + if (wpa_tdls_addset_peer(sm, peer, 0) < 0) + goto error; + + wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Setup Confirm / " + "TPK Handshake Message 3"); + if (wpa_tdls_send_tpk_m3(sm, src_addr, dtoken, lnkid, peer) < 0) + goto error; + + if (!peer->tpk_success) { + /* + * Enable Link only when tpk_success is 0, signifying that this + * processing of TPK M2 frame is not because of a retransmission + * during TDLS setup handshake. + */ + ret = wpa_tdls_enable_link(sm, peer); + if (ret < 0) { + wpa_printf(MSG_DEBUG, "TDLS: Could not enable link"); + wpa_tdls_do_teardown( + sm, peer, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + } + } + return ret; + +error: + wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, dtoken, 1, + status); + wpa_tdls_disable_peer_link(sm, peer); + return -1; +} + + +static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, + const u8 *buf, size_t len) +{ + struct wpa_tdls_peer *peer; + struct wpa_eapol_ie_parse kde; + struct wpa_tdls_ftie *ftie; + struct wpa_tdls_timeoutie *timeoutie; + struct wpa_tdls_lnkid *lnkid; + int ielen; + u16 status; + const u8 *pos; + u32 lifetime; + int ret = 0; + + wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Confirm / TPK M3 " + "(Peer " MACSTR ")", MAC2STR(src_addr)); + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0) + break; + } + if (peer == NULL) { + wpa_printf(MSG_INFO, "TDLS: No matching peer found for " + "TPK M3: " MACSTR, MAC2STR(src_addr)); + return -1; + } + wpa_tdls_tpk_retry_timeout_cancel(sm, peer, WLAN_TDLS_SETUP_RESPONSE); + + if (len < 3 + 3) + goto error; + pos = buf; + pos += 1 /* pkt_type */ + 1 /* Category */ + 1 /* Action */; + + status = WPA_GET_LE16(pos); + + if (status != 0) { + wpa_printf(MSG_INFO, "TDLS: Status code in TPK M3: %u", + status); + goto error; + } + pos += 2 /* status code */ + 1 /* dialog token */; + + ielen = len - (pos - buf); /* start of IE in buf */ + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs piggy-back broken IEs to the end + * of a TDLS Confirm packet, which will fail the link if we don't ignore + * this error. + */ + if (wpa_supplicant_parse_ies((const u8 *) pos, ielen, &kde) < 0) { + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse KDEs in TPK M3 - ignore as an interop workaround"); + } + + if (kde.lnkid == NULL || kde.lnkid_len < 3 * ETH_ALEN) { + wpa_printf(MSG_INFO, "TDLS: No Link Identifier IE in TPK M3"); + goto error; + } + wpa_hexdump(MSG_DEBUG, "TDLS: Link ID Received from TPK M3", + (u8 *) kde.lnkid, kde.lnkid_len); + lnkid = (struct wpa_tdls_lnkid *) kde.lnkid; + + if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) { + wpa_printf(MSG_INFO, "TDLS: TPK M3 from diff BSS"); + goto error; + } + + if (!wpa_tdls_get_privacy(sm)) + goto skip_rsn; + + if (kde.ftie == NULL || kde.ftie_len < sizeof(*ftie)) { + wpa_printf(MSG_INFO, "TDLS: No FTIE in TPK M3"); + goto error; + } + wpa_hexdump(MSG_DEBUG, "TDLS: FTIE Received from TPK M3", + kde.ftie, sizeof(*ftie)); + ftie = (struct wpa_tdls_ftie *) kde.ftie; + + if (kde.rsn_ie == NULL) { + wpa_printf(MSG_INFO, "TDLS: No RSN IE in TPK M3"); + goto error; + } + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE Received from TPK M3", + kde.rsn_ie, kde.rsn_ie_len); + if (kde.rsn_ie_len != peer->rsnie_p_len || + os_memcmp(kde.rsn_ie, peer->rsnie_p, peer->rsnie_p_len) != 0) { + wpa_printf(MSG_INFO, "TDLS: RSN IE in TPK M3 does not match " + "with the one sent in TPK M2"); + goto error; + } + + if (!os_memcmp(peer->rnonce, ftie->Anonce, WPA_NONCE_LEN) == 0) { + wpa_printf(MSG_INFO, "TDLS: FTIE ANonce in TPK M3 does " + "not match with FTIE ANonce used in TPK M2"); + goto error; + } + + if (!os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) == 0) { + wpa_printf(MSG_INFO, "TDLS: FTIE SNonce in TPK M3 does not " + "match with FTIE SNonce used in TPK M1"); + goto error; + } + + if (kde.key_lifetime == NULL) { + wpa_printf(MSG_INFO, "TDLS: No Key Lifetime IE in TPK M3"); + goto error; + } + timeoutie = (struct wpa_tdls_timeoutie *) kde.key_lifetime; + wpa_hexdump(MSG_DEBUG, "TDLS: Timeout IE Received from TPK M3", + (u8 *) timeoutie, sizeof(*timeoutie)); + lifetime = WPA_GET_LE32(timeoutie->value); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds in TPK M3", + lifetime); + if (lifetime != peer->lifetime) { + wpa_printf(MSG_INFO, "TDLS: Unexpected TPK lifetime %u in " + "TPK M3 (expected %u)", lifetime, peer->lifetime); + goto error; + } + + if (wpa_supplicant_verify_tdls_mic(3, peer, (u8 *) lnkid, + (u8 *) timeoutie, ftie) < 0) { + wpa_tdls_del_key(sm, peer); + goto error; + } + + if (wpa_tdls_set_key(sm, peer) < 0) { + /* + * Some drivers may not be able to config the key prior to full + * STA entry having been configured. + */ + wpa_printf(MSG_DEBUG, "TDLS: Try to configure TPK again after " + "STA entry is complete"); + peer->reconfig_key = 1; + } + +skip_rsn: + /* add supported rates, capabilities, and qos_info to the TDLS peer */ + if (wpa_tdls_addset_peer(sm, peer, 0) < 0) + goto error; + + if (!peer->tpk_success) { + /* + * Enable Link only when tpk_success is 0, signifying that this + * processing of TPK M3 frame is not because of a retransmission + * during TDLS setup handshake. + */ + ret = wpa_tdls_enable_link(sm, peer); + if (ret < 0) { + wpa_printf(MSG_DEBUG, "TDLS: Could not enable link"); + goto error; + } + } + return ret; +error: + wpa_tdls_do_teardown(sm, peer, WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + return -1; +} + + +static u8 * wpa_add_tdls_timeoutie(u8 *pos, u8 *ie, size_t ie_len, u32 tsecs) +{ + struct wpa_tdls_timeoutie *lifetime = (struct wpa_tdls_timeoutie *) ie; + + os_memset(lifetime, 0, ie_len); + lifetime->ie_type = WLAN_EID_TIMEOUT_INTERVAL; + lifetime->ie_len = sizeof(struct wpa_tdls_timeoutie) - 2; + lifetime->interval_type = WLAN_TIMEOUT_KEY_LIFETIME; + WPA_PUT_LE32(lifetime->value, tsecs); + os_memcpy(pos, ie, ie_len); + return pos + ie_len; +} + + +/** + * wpa_tdls_start - Initiate TDLS handshake (send TPK Handshake Message 1) + * @sm: Pointer to WPA state machine data from wpa_sm_init() + * @peer: MAC address of the peer STA + * Returns: 0 on success, or -1 on failure + * + * Send TPK Handshake Message 1 info to driver to start TDLS + * handshake with the peer. + */ +int wpa_tdls_start(struct wpa_sm *sm, const u8 *addr) +{ + struct wpa_tdls_peer *peer; + int tdls_prohibited = sm->tdls_prohibited; + + if (sm->tdls_disabled || !sm->tdls_supported) + return -1; + +#ifdef CONFIG_TDLS_TESTING + if ((tdls_testing & TDLS_TESTING_IGNORE_AP_PROHIBIT) && + tdls_prohibited) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - ignore AP prohibition " + "on TDLS"); + tdls_prohibited = 0; + } +#endif /* CONFIG_TDLS_TESTING */ + + if (tdls_prohibited) { + wpa_printf(MSG_DEBUG, "TDLS: TDLS is prohibited in this BSS - " + "reject request to start setup"); + return -1; + } + + peer = wpa_tdls_add_peer(sm, addr, NULL); + if (peer == NULL) + return -1; + + if (peer->tpk_in_progress) { + wpa_printf(MSG_DEBUG, "TDLS: Setup is already in progress with the peer"); + return 0; + } + + peer->initiator = 1; + + /* add the peer to the driver as a "setup in progress" peer */ + if (wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, 0, NULL, 0, NULL, + NULL, 0, 0, NULL, 0, NULL, 0, NULL, 0)) { + wpa_tdls_disable_peer_link(sm, peer); + return -1; + } + + peer->tpk_in_progress = 1; + + if (wpa_tdls_send_tpk_m1(sm, peer) < 0) { + wpa_tdls_disable_peer_link(sm, peer); + return -1; + } + + return 0; +} + + +void wpa_tdls_remove(struct wpa_sm *sm, const u8 *addr) +{ + struct wpa_tdls_peer *peer; + + if (sm->tdls_disabled || !sm->tdls_supported) + return; + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (peer == NULL || !peer->tpk_success) + return; + + if (sm->tdls_external_setup) { + /* + * Disable previous link to allow renegotiation to be completed + * on AP path. + */ + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + } +} + + +/** + * wpa_supplicant_rx_tdls - Receive TDLS data frame + * + * This function is called to receive TDLS (ethertype = 0x890d) data frames. + */ +static void wpa_supplicant_rx_tdls(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len) +{ + struct wpa_sm *sm = ctx; + struct wpa_tdls_frame *tf; + + wpa_hexdump(MSG_DEBUG, "TDLS: Received Data frame encapsulation", + buf, len); + + if (sm->tdls_disabled || !sm->tdls_supported) { + wpa_printf(MSG_DEBUG, "TDLS: Discard message - TDLS disabled " + "or unsupported by driver"); + return; + } + + if (os_memcmp(src_addr, sm->own_addr, ETH_ALEN) == 0) { + wpa_printf(MSG_DEBUG, "TDLS: Discard copy of own message"); + return; + } + + if (len < sizeof(*tf)) { + wpa_printf(MSG_INFO, "TDLS: Drop too short frame"); + return; + } + + /* Check to make sure its a valid encapsulated TDLS frame */ + tf = (struct wpa_tdls_frame *) buf; + if (tf->payloadtype != 2 /* TDLS_RFTYPE */ || + tf->category != WLAN_ACTION_TDLS) { + wpa_printf(MSG_INFO, "TDLS: Invalid frame - payloadtype=%u " + "category=%u action=%u", + tf->payloadtype, tf->category, tf->action); + return; + } + + switch (tf->action) { + case WLAN_TDLS_SETUP_REQUEST: + wpa_tdls_process_tpk_m1(sm, src_addr, buf, len); + break; + case WLAN_TDLS_SETUP_RESPONSE: + wpa_tdls_process_tpk_m2(sm, src_addr, buf, len); + break; + case WLAN_TDLS_SETUP_CONFIRM: + wpa_tdls_process_tpk_m3(sm, src_addr, buf, len); + break; + case WLAN_TDLS_TEARDOWN: + wpa_tdls_recv_teardown(sm, src_addr, buf, len); + break; + case WLAN_TDLS_DISCOVERY_REQUEST: + wpa_tdls_process_discovery_request(sm, src_addr, buf, len); + break; + default: + /* Kernel code will process remaining frames */ + wpa_printf(MSG_DEBUG, "TDLS: Ignore TDLS frame action code %u", + tf->action); + break; + } +} + + +/** + * wpa_tdls_init - Initialize driver interface parameters for TDLS + * @wpa_s: Pointer to wpa_supplicant data + * Returns: 0 on success, -1 on failure + * + * This function is called to initialize driver interface parameters for TDLS. + * wpa_drv_init() must have been called before this function to initialize the + * driver interface. + */ +int wpa_tdls_init(struct wpa_sm *sm) +{ + if (sm == NULL) + return -1; + + sm->l2_tdls = l2_packet_init(sm->bridge_ifname ? sm->bridge_ifname : + sm->ifname, + sm->own_addr, + ETH_P_80211_ENCAP, wpa_supplicant_rx_tdls, + sm, 0); + if (sm->l2_tdls == NULL) { + wpa_printf(MSG_ERROR, "TDLS: Failed to open l2_packet " + "connection"); + return -1; + } + + /* + * Drivers that support TDLS but don't implement the get_capa callback + * are assumed to perform everything internally + */ + if (wpa_sm_tdls_get_capa(sm, &sm->tdls_supported, + &sm->tdls_external_setup, + &sm->tdls_chan_switch) < 0) { + sm->tdls_supported = 1; + sm->tdls_external_setup = 0; + } + + wpa_printf(MSG_DEBUG, "TDLS: TDLS operation%s supported by " + "driver", sm->tdls_supported ? "" : " not"); + wpa_printf(MSG_DEBUG, "TDLS: Driver uses %s link setup", + sm->tdls_external_setup ? "external" : "internal"); + wpa_printf(MSG_DEBUG, "TDLS: Driver %s TDLS channel switching", + sm->tdls_chan_switch ? "supports" : "does not support"); + + return 0; +} + + +void wpa_tdls_teardown_peers(struct wpa_sm *sm) +{ + struct wpa_tdls_peer *peer, *tmp; + + if (!sm) + return; + peer = sm->tdls; + + wpa_printf(MSG_DEBUG, "TDLS: Tear down peers"); + + while (peer) { + tmp = peer->next; + wpa_printf(MSG_DEBUG, "TDLS: Tear down peer " MACSTR, + MAC2STR(peer->addr)); + if (sm->tdls_external_setup) + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_DEAUTH_LEAVING); + else + wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr); + + peer = tmp; + } +} + + +static void wpa_tdls_remove_peers(struct wpa_sm *sm) +{ + struct wpa_tdls_peer *peer, *tmp; + + peer = sm->tdls; + + while (peer) { + int res; + tmp = peer->next; + res = wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); + wpa_printf(MSG_DEBUG, "TDLS: Remove peer " MACSTR " (res=%d)", + MAC2STR(peer->addr), res); + wpa_tdls_peer_free(sm, peer); + peer = tmp; + } +} + + +/** + * wpa_tdls_deinit - Deinitialize driver interface parameters for TDLS + * + * This function is called to recover driver interface parameters for TDLS + * and frees resources allocated for it. + */ +void wpa_tdls_deinit(struct wpa_sm *sm) +{ + if (sm == NULL) + return; + + if (sm->l2_tdls) + l2_packet_deinit(sm->l2_tdls); + sm->l2_tdls = NULL; + + wpa_tdls_remove_peers(sm); +} + + +void wpa_tdls_assoc(struct wpa_sm *sm) +{ + wpa_printf(MSG_DEBUG, "TDLS: Remove peers on association"); + wpa_tdls_remove_peers(sm); +} + + +void wpa_tdls_disassoc(struct wpa_sm *sm) +{ + wpa_printf(MSG_DEBUG, "TDLS: Remove peers on disassociation"); + wpa_tdls_remove_peers(sm); +} + + +static int wpa_tdls_prohibited(struct ieee802_11_elems *elems) +{ + /* bit 38 - TDLS Prohibited */ + return !!(elems->ext_capab[2 + 4] & 0x40); +} + + +static int wpa_tdls_chan_switch_prohibited(struct ieee802_11_elems *elems) +{ + /* bit 39 - TDLS Channel Switch Prohibited */ + return !!(elems->ext_capab[2 + 4] & 0x80); +} + + +void wpa_tdls_ap_ies(struct wpa_sm *sm, const u8 *ies, size_t len) +{ + struct ieee802_11_elems elems; + + sm->tdls_prohibited = 0; + sm->tdls_chan_switch_prohibited = 0; + + if (ies == NULL || + ieee802_11_parse_elems(ies, len, &elems, 0) == ParseFailed || + elems.ext_capab == NULL || elems.ext_capab_len < 2 + 5) + return; + + sm->tdls_prohibited = wpa_tdls_prohibited(&elems); + wpa_printf(MSG_DEBUG, "TDLS: TDLS is %s in the target BSS", + sm->tdls_prohibited ? "prohibited" : "allowed"); + sm->tdls_chan_switch_prohibited = + wpa_tdls_chan_switch_prohibited(&elems); + wpa_printf(MSG_DEBUG, "TDLS: TDLS channel switch %s in the target BSS", + sm->tdls_chan_switch_prohibited ? "prohibited" : "allowed"); +} + + +void wpa_tdls_assoc_resp_ies(struct wpa_sm *sm, const u8 *ies, size_t len) +{ + struct ieee802_11_elems elems; + + if (ies == NULL || + ieee802_11_parse_elems(ies, len, &elems, 0) == ParseFailed || + elems.ext_capab == NULL || elems.ext_capab_len < 2 + 5) + return; + + if (!sm->tdls_prohibited && wpa_tdls_prohibited(&elems)) { + wpa_printf(MSG_DEBUG, "TDLS: TDLS prohibited based on " + "(Re)Association Response IEs"); + sm->tdls_prohibited = 1; + } + + if (!sm->tdls_chan_switch_prohibited && + wpa_tdls_chan_switch_prohibited(&elems)) { + wpa_printf(MSG_DEBUG, + "TDLS: TDLS channel switch prohibited based on (Re)Association Response IEs"); + sm->tdls_chan_switch_prohibited = 1; + } +} + + +void wpa_tdls_enable(struct wpa_sm *sm, int enabled) +{ + wpa_printf(MSG_DEBUG, "TDLS: %s", enabled ? "enabled" : "disabled"); + sm->tdls_disabled = !enabled; +} + + +int wpa_tdls_is_external_setup(struct wpa_sm *sm) +{ + return sm->tdls_external_setup; +} + + +int wpa_tdls_enable_chan_switch(struct wpa_sm *sm, const u8 *addr, + u8 oper_class, + struct hostapd_freq_params *freq_params) +{ + struct wpa_tdls_peer *peer; + int ret; + + if (sm->tdls_disabled || !sm->tdls_supported) + return -1; + + if (!sm->tdls_chan_switch) { + wpa_printf(MSG_DEBUG, + "TDLS: Channel switching not supported by the driver"); + return -1; + } + + if (sm->tdls_chan_switch_prohibited) { + wpa_printf(MSG_DEBUG, + "TDLS: Channel switching is prohibited in this BSS - reject request to switch channel"); + return -1; + } + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (peer == NULL || !peer->tpk_success) { + wpa_printf(MSG_ERROR, "TDLS: Peer " MACSTR + " not found for channel switching", MAC2STR(addr)); + return -1; + } + + if (peer->chan_switch_enabled) { + wpa_printf(MSG_DEBUG, "TDLS: Peer " MACSTR + " already has channel switching enabled", + MAC2STR(addr)); + return 0; + } + + ret = wpa_sm_tdls_enable_channel_switch(sm, peer->addr, + oper_class, freq_params); + if (!ret) + peer->chan_switch_enabled = 1; + + return ret; +} + + +int wpa_tdls_disable_chan_switch(struct wpa_sm *sm, const u8 *addr) +{ + struct wpa_tdls_peer *peer; + + if (sm->tdls_disabled || !sm->tdls_supported) + return -1; + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (!peer || !peer->chan_switch_enabled) { + wpa_printf(MSG_ERROR, "TDLS: Channel switching not enabled for " + MACSTR, MAC2STR(addr)); + return -1; + } + + /* ignore the return value */ + wpa_sm_tdls_disable_channel_switch(sm, peer->addr); + + peer->chan_switch_enabled = 0; + return 0; +} diff --git a/freebsd/contrib/wpa/src/utils/bitfield.h b/freebsd/contrib/wpa/src/utils/bitfield.h new file mode 100644 index 00000000..7050a208 --- /dev/null +++ b/freebsd/contrib/wpa/src/utils/bitfield.h @@ -0,0 +1,21 @@ +/* + * Bitfield + * Copyright (c) 2013, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BITFIELD_H +#define BITFIELD_H + +struct bitfield; + +struct bitfield * bitfield_alloc(size_t max_bits); +void bitfield_free(struct bitfield *bf); +void bitfield_set(struct bitfield *bf, size_t bit); +void bitfield_clear(struct bitfield *bf, size_t bit); +int bitfield_is_set(struct bitfield *bf, size_t bit); +int bitfield_get_first_zero(struct bitfield *bf); + +#endif /* BITFIELD_H */ diff --git a/freebsd/contrib/wpa/wpa_supplicant/wnm_sta.c b/freebsd/contrib/wpa/wpa_supplicant/wnm_sta.c new file mode 100644 index 00000000..ea737e31 --- /dev/null +++ b/freebsd/contrib/wpa/wpa_supplicant/wnm_sta.c @@ -0,0 +1,1150 @@ +#include + +/* + * wpa_supplicant - WNM + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. + * + * 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 "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/wpa_ctrl.h" +#include "rsn_supp/wpa.h" +#include "wpa_supplicant_i.h" +#include "driver_i.h" +#include "scan.h" +#include "ctrl_iface.h" +#include "bss.h" +#include "wnm_sta.h" +#include "hs20_supplicant.h" + +#define MAX_TFS_IE_LEN 1024 +#define WNM_MAX_NEIGHBOR_REPORT 10 + + +/* get the TFS IE from driver */ +static int ieee80211_11_get_tfs_ie(struct wpa_supplicant *wpa_s, u8 *buf, + u16 *buf_len, enum wnm_oper oper) +{ + wpa_printf(MSG_DEBUG, "%s: TFS get operation %d", __func__, oper); + + return wpa_drv_wnm_oper(wpa_s, oper, wpa_s->bssid, buf, buf_len); +} + + +/* set the TFS IE to driver */ +static int ieee80211_11_set_tfs_ie(struct wpa_supplicant *wpa_s, + const u8 *addr, u8 *buf, u16 *buf_len, + enum wnm_oper oper) +{ + wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper); + + return wpa_drv_wnm_oper(wpa_s, oper, addr, buf, buf_len); +} + + +/* MLME-SLEEPMODE.request */ +int ieee802_11_send_wnmsleep_req(struct wpa_supplicant *wpa_s, + u8 action, u16 intval, struct wpabuf *tfs_req) +{ + struct ieee80211_mgmt *mgmt; + int res; + size_t len; + struct wnm_sleep_element *wnmsleep_ie; + u8 *wnmtfs_ie; + u8 wnmsleep_ie_len; + u16 wnmtfs_ie_len; /* possibly multiple IE(s) */ + enum wnm_oper tfs_oper = action == 0 ? WNM_SLEEP_TFS_REQ_IE_ADD : + WNM_SLEEP_TFS_REQ_IE_NONE; + + wpa_printf(MSG_DEBUG, "WNM: Request to send WNM-Sleep Mode Request " + "action=%s to " MACSTR, + action == 0 ? "enter" : "exit", + MAC2STR(wpa_s->bssid)); + + /* WNM-Sleep Mode IE */ + wnmsleep_ie_len = sizeof(struct wnm_sleep_element); + wnmsleep_ie = os_zalloc(sizeof(struct wnm_sleep_element)); + if (wnmsleep_ie == NULL) + return -1; + wnmsleep_ie->eid = WLAN_EID_WNMSLEEP; + wnmsleep_ie->len = wnmsleep_ie_len - 2; + wnmsleep_ie->action_type = action; + wnmsleep_ie->status = WNM_STATUS_SLEEP_ACCEPT; + wnmsleep_ie->intval = host_to_le16(intval); + wpa_hexdump(MSG_DEBUG, "WNM: WNM-Sleep Mode element", + (u8 *) wnmsleep_ie, wnmsleep_ie_len); + + /* TFS IE(s) */ + if (tfs_req) { + wnmtfs_ie_len = wpabuf_len(tfs_req); + wnmtfs_ie = os_malloc(wnmtfs_ie_len); + if (wnmtfs_ie == NULL) { + os_free(wnmsleep_ie); + return -1; + } + os_memcpy(wnmtfs_ie, wpabuf_head(tfs_req), wnmtfs_ie_len); + } else { + wnmtfs_ie = os_zalloc(MAX_TFS_IE_LEN); + if (wnmtfs_ie == NULL) { + os_free(wnmsleep_ie); + return -1; + } + if (ieee80211_11_get_tfs_ie(wpa_s, wnmtfs_ie, &wnmtfs_ie_len, + tfs_oper)) { + wnmtfs_ie_len = 0; + os_free(wnmtfs_ie); + wnmtfs_ie = NULL; + } + } + wpa_hexdump(MSG_DEBUG, "WNM: TFS Request element", + (u8 *) wnmtfs_ie, wnmtfs_ie_len); + + mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len + wnmtfs_ie_len); + if (mgmt == NULL) { + wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for " + "WNM-Sleep Request action frame"); + os_free(wnmsleep_ie); + os_free(wnmtfs_ie); + return -1; + } + + os_memcpy(mgmt->da, wpa_s->bssid, ETH_ALEN); + os_memcpy(mgmt->sa, wpa_s->own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, wpa_s->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.wnm_sleep_req.action = WNM_SLEEP_MODE_REQ; + mgmt->u.action.u.wnm_sleep_req.dialogtoken = 1; + os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable, wnmsleep_ie, + wnmsleep_ie_len); + /* copy TFS IE here */ + if (wnmtfs_ie_len > 0) { + os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable + + wnmsleep_ie_len, wnmtfs_ie, wnmtfs_ie_len); + } + + len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_req) + wnmsleep_ie_len + + wnmtfs_ie_len; + + res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, + wpa_s->own_addr, wpa_s->bssid, + &mgmt->u.action.category, len, 0); + if (res < 0) + wpa_printf(MSG_DEBUG, "Failed to send WNM-Sleep Request " + "(action=%d, intval=%d)", action, intval); + + os_free(wnmsleep_ie); + os_free(wnmtfs_ie); + os_free(mgmt); + + return res; +} + + +static void wnm_sleep_mode_enter_success(struct wpa_supplicant *wpa_s, + u8 *tfsresp_ie_start, + u8 *tfsresp_ie_end) +{ + wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_ENTER_CONFIRM, + wpa_s->bssid, NULL, NULL); + /* remove GTK/IGTK ?? */ + + /* set the TFS Resp IE(s) */ + if (tfsresp_ie_start && tfsresp_ie_end && + tfsresp_ie_end - tfsresp_ie_start >= 0) { + u16 tfsresp_ie_len; + tfsresp_ie_len = (tfsresp_ie_end + tfsresp_ie_end[1] + 2) - + tfsresp_ie_start; + wpa_printf(MSG_DEBUG, "TFS Resp IE(s) found"); + /* pass the TFS Resp IE(s) to driver for processing */ + if (ieee80211_11_set_tfs_ie(wpa_s, wpa_s->bssid, + tfsresp_ie_start, + &tfsresp_ie_len, + WNM_SLEEP_TFS_RESP_IE_SET)) + wpa_printf(MSG_DEBUG, "WNM: Fail to set TFS Resp IE"); + } +} + + +static void wnm_sleep_mode_exit_success(struct wpa_supplicant *wpa_s, + const u8 *frm, u16 key_len_total) +{ + u8 *ptr, *end; + u8 gtk_len; + + wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_EXIT_CONFIRM, wpa_s->bssid, + NULL, NULL); + + /* Install GTK/IGTK */ + + /* point to key data field */ + ptr = (u8 *) frm + 1 + 2; + end = ptr + key_len_total; + wpa_hexdump_key(MSG_DEBUG, "WNM: Key Data", ptr, key_len_total); + + while (ptr + 1 < end) { + if (ptr + 2 + ptr[1] > end) { + wpa_printf(MSG_DEBUG, "WNM: Invalid Key Data element " + "length"); + if (end > ptr) { + wpa_hexdump(MSG_DEBUG, "WNM: Remaining data", + ptr, end - ptr); + } + break; + } + if (*ptr == WNM_SLEEP_SUBELEM_GTK) { + if (ptr[1] < 11 + 5) { + wpa_printf(MSG_DEBUG, "WNM: Too short GTK " + "subelem"); + break; + } + gtk_len = *(ptr + 4); + if (ptr[1] < 11 + gtk_len || + gtk_len < 5 || gtk_len > 32) { + wpa_printf(MSG_DEBUG, "WNM: Invalid GTK " + "subelem"); + break; + } + wpa_wnmsleep_install_key( + wpa_s->wpa, + WNM_SLEEP_SUBELEM_GTK, + ptr); + ptr += 13 + gtk_len; +#ifdef CONFIG_IEEE80211W + } else if (*ptr == WNM_SLEEP_SUBELEM_IGTK) { + if (ptr[1] < 2 + 6 + WPA_IGTK_LEN) { + wpa_printf(MSG_DEBUG, "WNM: Too short IGTK " + "subelem"); + break; + } + wpa_wnmsleep_install_key(wpa_s->wpa, + WNM_SLEEP_SUBELEM_IGTK, ptr); + ptr += 10 + WPA_IGTK_LEN; +#endif /* CONFIG_IEEE80211W */ + } else + break; /* skip the loop */ + } +} + + +static void ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant *wpa_s, + const u8 *frm, int len) +{ + /* + * Action [1] | Dialog Token [1] | Key Data Len [2] | Key Data | + * WNM-Sleep Mode IE | TFS Response IE + */ + u8 *pos = (u8 *) frm; /* point to payload after the action field */ + u16 key_len_total; + struct wnm_sleep_element *wnmsleep_ie = NULL; + /* multiple TFS Resp IE (assuming consecutive) */ + u8 *tfsresp_ie_start = NULL; + u8 *tfsresp_ie_end = NULL; + size_t left; + + if (len < 3) + return; + key_len_total = WPA_GET_LE16(frm + 1); + + wpa_printf(MSG_DEBUG, "WNM-Sleep Mode Response token=%u key_len_total=%d", + frm[0], key_len_total); + left = len - 3; + if (key_len_total > left) { + wpa_printf(MSG_INFO, "WNM: Too short frame for Key Data field"); + return; + } + pos += 3 + key_len_total; + while (pos - frm < len) { + u8 ie_len = *(pos + 1); + if (pos + 2 + ie_len > frm + len) { + wpa_printf(MSG_INFO, "WNM: Invalid IE len %u", ie_len); + break; + } + wpa_hexdump(MSG_DEBUG, "WNM: Element", pos, 2 + ie_len); + if (*pos == WLAN_EID_WNMSLEEP) + wnmsleep_ie = (struct wnm_sleep_element *) pos; + else if (*pos == WLAN_EID_TFS_RESP) { + if (!tfsresp_ie_start) + tfsresp_ie_start = pos; + tfsresp_ie_end = pos; + } else + wpa_printf(MSG_DEBUG, "EID %d not recognized", *pos); + pos += ie_len + 2; + } + + if (!wnmsleep_ie) { + wpa_printf(MSG_DEBUG, "No WNM-Sleep IE found"); + return; + } + + if (wnmsleep_ie->status == WNM_STATUS_SLEEP_ACCEPT || + wnmsleep_ie->status == WNM_STATUS_SLEEP_EXIT_ACCEPT_GTK_UPDATE) { + wpa_printf(MSG_DEBUG, "Successfully recv WNM-Sleep Response " + "frame (action=%d, intval=%d)", + wnmsleep_ie->action_type, wnmsleep_ie->intval); + if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER) { + wnm_sleep_mode_enter_success(wpa_s, tfsresp_ie_start, + tfsresp_ie_end); + } else if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) { + wnm_sleep_mode_exit_success(wpa_s, frm, key_len_total); + } + } else { + wpa_printf(MSG_DEBUG, "Reject recv WNM-Sleep Response frame " + "(action=%d, intval=%d)", + wnmsleep_ie->action_type, wnmsleep_ie->intval); + if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER) + wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_ENTER_FAIL, + wpa_s->bssid, NULL, NULL); + else if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) + wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_EXIT_FAIL, + wpa_s->bssid, NULL, NULL); + } +} + + +void wnm_deallocate_memory(struct wpa_supplicant *wpa_s) +{ + int i; + + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { + os_free(wpa_s->wnm_neighbor_report_elements[i].meas_pilot); + os_free(wpa_s->wnm_neighbor_report_elements[i].mul_bssid); + } + + wpa_s->wnm_num_neighbor_report = 0; + os_free(wpa_s->wnm_neighbor_report_elements); + wpa_s->wnm_neighbor_report_elements = NULL; +} + + +static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, + u8 id, u8 elen, const u8 *pos) +{ + switch (id) { + case WNM_NEIGHBOR_TSF: + if (elen < 2 + 2) { + wpa_printf(MSG_DEBUG, "WNM: Too short TSF"); + break; + } + rep->tsf_offset = WPA_GET_LE16(pos); + rep->beacon_int = WPA_GET_LE16(pos + 2); + rep->tsf_present = 1; + break; + case WNM_NEIGHBOR_CONDENSED_COUNTRY_STRING: + if (elen < 2) { + wpa_printf(MSG_DEBUG, "WNM: Too short condensed " + "country string"); + break; + } + os_memcpy(rep->country, pos, 2); + rep->country_present = 1; + break; + case WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE: + if (elen < 1) { + wpa_printf(MSG_DEBUG, "WNM: Too short BSS transition " + "candidate"); + break; + } + rep->preference = pos[0]; + rep->preference_present = 1; + break; + case WNM_NEIGHBOR_BSS_TERMINATION_DURATION: + rep->bss_term_tsf = WPA_GET_LE64(pos); + rep->bss_term_dur = WPA_GET_LE16(pos + 8); + rep->bss_term_present = 1; + break; + case WNM_NEIGHBOR_BEARING: + if (elen < 8) { + wpa_printf(MSG_DEBUG, "WNM: Too short neighbor " + "bearing"); + break; + } + rep->bearing = WPA_GET_LE16(pos); + rep->distance = WPA_GET_LE32(pos + 2); + rep->rel_height = WPA_GET_LE16(pos + 2 + 4); + rep->bearing_present = 1; + break; + case WNM_NEIGHBOR_MEASUREMENT_PILOT: + if (elen < 1) { + wpa_printf(MSG_DEBUG, "WNM: Too short measurement " + "pilot"); + break; + } + os_free(rep->meas_pilot); + rep->meas_pilot = os_zalloc(sizeof(struct measurement_pilot)); + if (rep->meas_pilot == NULL) + break; + rep->meas_pilot->measurement_pilot = pos[0]; + rep->meas_pilot->subelem_len = elen - 1; + os_memcpy(rep->meas_pilot->subelems, pos + 1, elen - 1); + break; + case WNM_NEIGHBOR_RRM_ENABLED_CAPABILITIES: + if (elen < 5) { + wpa_printf(MSG_DEBUG, "WNM: Too short RRM enabled " + "capabilities"); + break; + } + os_memcpy(rep->rm_capab, pos, 5); + rep->rm_capab_present = 1; + break; + case WNM_NEIGHBOR_MULTIPLE_BSSID: + if (elen < 1) { + wpa_printf(MSG_DEBUG, "WNM: Too short multiple BSSID"); + break; + } + os_free(rep->mul_bssid); + rep->mul_bssid = os_zalloc(sizeof(struct multiple_bssid)); + if (rep->mul_bssid == NULL) + break; + rep->mul_bssid->max_bssid_indicator = pos[0]; + rep->mul_bssid->subelem_len = elen - 1; + os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1); + break; + } +} + + +static int wnm_nei_get_chan(struct wpa_supplicant *wpa_s, u8 op_class, u8 chan) +{ + struct wpa_bss *bss = wpa_s->current_bss; + const char *country = NULL; + + if (bss) { + const u8 *elem = wpa_bss_get_ie(bss, WLAN_EID_COUNTRY); + + if (elem && elem[1] >= 2) + country = (const char *) (elem + 2); + } + + return ieee80211_chan_to_freq(country, op_class, chan); +} + + +static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, + const u8 *pos, u8 len, + struct neighbor_report *rep) +{ + u8 left = len; + + if (left < 13) { + wpa_printf(MSG_DEBUG, "WNM: Too short neighbor report"); + return; + } + + os_memcpy(rep->bssid, pos, ETH_ALEN); + rep->bssid_info = WPA_GET_LE32(pos + ETH_ALEN); + rep->regulatory_class = *(pos + 10); + rep->channel_number = *(pos + 11); + rep->phy_type = *(pos + 12); + + pos += 13; + left -= 13; + + while (left >= 2) { + u8 id, elen; + + id = *pos++; + elen = *pos++; + wpa_printf(MSG_DEBUG, "WNM: Subelement id=%u len=%u", id, elen); + left -= 2; + if (elen > left) { + wpa_printf(MSG_DEBUG, + "WNM: Truncated neighbor report subelement"); + break; + } + wnm_parse_neighbor_report_elem(rep, id, elen, pos); + left -= elen; + pos += elen; + } + + rep->freq = wnm_nei_get_chan(wpa_s, rep->regulatory_class, + rep->channel_number); +} + + +static struct wpa_bss * +compare_scan_neighbor_results(struct wpa_supplicant *wpa_s) +{ + + u8 i; + struct wpa_bss *bss = wpa_s->current_bss; + struct wpa_bss *target; + + if (!bss) + return 0; + + wpa_printf(MSG_DEBUG, "WNM: Current BSS " MACSTR " RSSI %d", + MAC2STR(wpa_s->bssid), bss->level); + + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { + struct neighbor_report *nei; + + nei = &wpa_s->wnm_neighbor_report_elements[i]; + if (nei->preference_present && nei->preference == 0) { + wpa_printf(MSG_DEBUG, "Skip excluded BSS " MACSTR, + MAC2STR(nei->bssid)); + continue; + } + + target = wpa_bss_get_bssid(wpa_s, nei->bssid); + if (!target) { + wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR + " (pref %d) not found in scan results", + MAC2STR(nei->bssid), + nei->preference_present ? nei->preference : + -1); + continue; + } + + if (bss->ssid_len != target->ssid_len || + os_memcmp(bss->ssid, target->ssid, bss->ssid_len) != 0) { + /* + * TODO: Could consider allowing transition to another + * ESS if PMF was enabled for the association. + */ + wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR + " (pref %d) in different ESS", + MAC2STR(nei->bssid), + nei->preference_present ? nei->preference : + -1); + continue; + } + + if (target->level < bss->level && target->level < -80) { + wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR + " (pref %d) does not have sufficient signal level (%d)", + MAC2STR(nei->bssid), + nei->preference_present ? nei->preference : + -1, + target->level); + continue; + } + + wpa_printf(MSG_DEBUG, + "WNM: Found an acceptable preferred transition candidate BSS " + MACSTR " (RSSI %d)", + MAC2STR(nei->bssid), target->level); + return target; + } + + return NULL; +} + + +static void wnm_send_bss_transition_mgmt_resp( + struct wpa_supplicant *wpa_s, u8 dialog_token, + enum bss_trans_mgmt_status_code status, u8 delay, + const u8 *target_bssid) +{ + u8 buf[1000], *pos; + struct ieee80211_mgmt *mgmt; + size_t len; + int res; + + wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Response " + "to " MACSTR " dialog_token=%u status=%u delay=%d", + MAC2STR(wpa_s->bssid), dialog_token, status, delay); + if (!wpa_s->current_bss) { + wpa_printf(MSG_DEBUG, + "WNM: Current BSS not known - drop response"); + return; + } + + mgmt = (struct ieee80211_mgmt *) buf; + os_memset(&buf, 0, sizeof(buf)); + os_memcpy(mgmt->da, wpa_s->bssid, ETH_ALEN); + os_memcpy(mgmt->sa, wpa_s->own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, wpa_s->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.bss_tm_resp.action = WNM_BSS_TRANS_MGMT_RESP; + mgmt->u.action.u.bss_tm_resp.dialog_token = dialog_token; + mgmt->u.action.u.bss_tm_resp.status_code = status; + mgmt->u.action.u.bss_tm_resp.bss_termination_delay = delay; + pos = mgmt->u.action.u.bss_tm_resp.variable; + if (target_bssid) { + os_memcpy(pos, target_bssid, ETH_ALEN); + pos += ETH_ALEN; + } else if (status == WNM_BSS_TM_ACCEPT) { + /* + * P802.11-REVmc clarifies that the Target BSSID field is always + * present when status code is zero, so use a fake value here if + * no BSSID is yet known. + */ + os_memset(pos, 0, ETH_ALEN); + pos += ETH_ALEN; + } + + len = pos - (u8 *) &mgmt->u.action.category; + + res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, + wpa_s->own_addr, wpa_s->bssid, + &mgmt->u.action.category, len, 0); + if (res < 0) { + wpa_printf(MSG_DEBUG, + "WNM: Failed to send BSS Transition Management Response"); + } +} + + +int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail) +{ + struct wpa_bss *bss; + struct wpa_ssid *ssid = wpa_s->current_ssid; + enum bss_trans_mgmt_status_code status = WNM_BSS_TM_REJECT_UNSPECIFIED; + + if (!wpa_s->wnm_neighbor_report_elements) + return 0; + + if (os_reltime_before(&wpa_s->wnm_cand_valid_until, + &wpa_s->scan_trigger_time)) { + wpa_printf(MSG_DEBUG, "WNM: Previously stored BSS transition candidate list is not valid anymore - drop it"); + wnm_deallocate_memory(wpa_s); + return 0; + } + + if (!wpa_s->current_bss || + os_memcmp(wpa_s->wnm_cand_from_bss, wpa_s->current_bss->bssid, + ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "WNM: Stored BSS transition candidate list not from the current BSS - ignore it"); + return 0; + } + + /* Compare the Neighbor Report and scan results */ + bss = compare_scan_neighbor_results(wpa_s); + if (!bss) { + wpa_printf(MSG_DEBUG, "WNM: No BSS transition candidate match found"); + status = WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES; + goto send_bss_resp_fail; + } + + /* Associate to the network */ + /* Send the BSS Management Response - Accept */ + if (wpa_s->wnm_reply) { + wpa_s->wnm_reply = 0; + wnm_send_bss_transition_mgmt_resp(wpa_s, + wpa_s->wnm_dialog_token, + WNM_BSS_TM_ACCEPT, + 0, bss->bssid); + } + + if (bss == wpa_s->current_bss) { + wpa_printf(MSG_DEBUG, + "WNM: Already associated with the preferred candidate"); + return 1; + } + + wpa_s->reassociate = 1; + wpa_supplicant_connect(wpa_s, bss, ssid); + wnm_deallocate_memory(wpa_s); + return 1; + +send_bss_resp_fail: + if (!reply_on_fail) + return 0; + + /* Send reject response for all the failures */ + + if (wpa_s->wnm_reply) { + wpa_s->wnm_reply = 0; + wnm_send_bss_transition_mgmt_resp(wpa_s, + wpa_s->wnm_dialog_token, + status, 0, NULL); + } + wnm_deallocate_memory(wpa_s); + + return 0; +} + + +static int cand_pref_compar(const void *a, const void *b) +{ + const struct neighbor_report *aa = a; + const struct neighbor_report *bb = b; + + if (!aa->preference_present && !bb->preference_present) + return 0; + if (!aa->preference_present) + return 1; + if (!bb->preference_present) + return -1; + if (bb->preference > aa->preference) + return 1; + if (bb->preference < aa->preference) + return -1; + return 0; +} + + +static void wnm_sort_cand_list(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->wnm_neighbor_report_elements) + return; + qsort(wpa_s->wnm_neighbor_report_elements, + wpa_s->wnm_num_neighbor_report, sizeof(struct neighbor_report), + cand_pref_compar); +} + + +static void wnm_dump_cand_list(struct wpa_supplicant *wpa_s) +{ + unsigned int i; + + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Candidate List"); + if (!wpa_s->wnm_neighbor_report_elements) + return; + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { + struct neighbor_report *nei; + + nei = &wpa_s->wnm_neighbor_report_elements[i]; + wpa_printf(MSG_DEBUG, "%u: " MACSTR + " info=0x%x op_class=%u chan=%u phy=%u pref=%d freq=%d", + i, MAC2STR(nei->bssid), nei->bssid_info, + nei->regulatory_class, + nei->channel_number, nei->phy_type, + nei->preference_present ? nei->preference : -1, + nei->freq); + } +} + + +static int chan_supported(struct wpa_supplicant *wpa_s, int freq) +{ + unsigned int i; + + for (i = 0; i < wpa_s->hw.num_modes; i++) { + struct hostapd_hw_modes *mode = &wpa_s->hw.modes[i]; + int j; + + for (j = 0; j < mode->num_channels; j++) { + struct hostapd_channel_data *chan; + + chan = &mode->channels[j]; + if (chan->freq == freq && + !(chan->flag & HOSTAPD_CHAN_DISABLED)) + return 1; + } + } + + return 0; +} + + +static void wnm_set_scan_freqs(struct wpa_supplicant *wpa_s) +{ + int *freqs; + int num_freqs = 0; + unsigned int i; + + if (!wpa_s->wnm_neighbor_report_elements) + return; + + if (wpa_s->hw.modes == NULL) + return; + + os_free(wpa_s->next_scan_freqs); + wpa_s->next_scan_freqs = NULL; + + freqs = os_calloc(wpa_s->wnm_num_neighbor_report + 1, sizeof(int)); + if (freqs == NULL) + return; + + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { + struct neighbor_report *nei; + + nei = &wpa_s->wnm_neighbor_report_elements[i]; + if (nei->freq <= 0) { + wpa_printf(MSG_DEBUG, + "WNM: Unknown neighbor operating frequency for " + MACSTR " - scan all channels", + MAC2STR(nei->bssid)); + os_free(freqs); + return; + } + if (chan_supported(wpa_s, nei->freq)) + add_freq(freqs, &num_freqs, nei->freq); + } + + if (num_freqs == 0) { + os_free(freqs); + return; + } + + wpa_printf(MSG_DEBUG, + "WNM: Scan %d frequencies based on transition candidate list", + num_freqs); + wpa_s->next_scan_freqs = freqs; +} + + +static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, + const u8 *pos, const u8 *end, + int reply) +{ + unsigned int beacon_int; + u8 valid_int; + + if (pos + 5 > end) + return; + + if (wpa_s->current_bss) + beacon_int = wpa_s->current_bss->beacon_int; + else + beacon_int = 100; /* best guess */ + + wpa_s->wnm_dialog_token = pos[0]; + wpa_s->wnm_mode = pos[1]; + wpa_s->wnm_dissoc_timer = WPA_GET_LE16(pos + 2); + valid_int = pos[4]; + wpa_s->wnm_reply = reply; + + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Request: " + "dialog_token=%u request_mode=0x%x " + "disassoc_timer=%u validity_interval=%u", + wpa_s->wnm_dialog_token, wpa_s->wnm_mode, + wpa_s->wnm_dissoc_timer, valid_int); + + pos += 5; + + if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) { + if (pos + 12 > end) { + wpa_printf(MSG_DEBUG, "WNM: Too short BSS TM Request"); + return; + } + os_memcpy(wpa_s->wnm_bss_termination_duration, pos, 12); + pos += 12; /* BSS Termination Duration */ + } + + if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) { + char url[256]; + + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "WNM: Invalid BSS Transition " + "Management Request (URL)"); + return; + } + os_memcpy(url, pos + 1, pos[0]); + url[pos[0]] = '\0'; + pos += 1 + pos[0]; + + wpa_msg(wpa_s, MSG_INFO, ESS_DISASSOC_IMMINENT "%d %u %s", + wpa_sm_pmf_enabled(wpa_s->wpa), + wpa_s->wnm_dissoc_timer * beacon_int * 128 / 125, url); + } + + if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) { + wpa_msg(wpa_s, MSG_INFO, "WNM: Disassociation Imminent - " + "Disassociation Timer %u", wpa_s->wnm_dissoc_timer); + if (wpa_s->wnm_dissoc_timer && !wpa_s->scanning) { + /* TODO: mark current BSS less preferred for + * selection */ + wpa_printf(MSG_DEBUG, "Trying to find another BSS"); + wpa_supplicant_req_scan(wpa_s, 0, 0); + } + } + + if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED) { + unsigned int valid_ms; + + wpa_msg(wpa_s, MSG_INFO, "WNM: Preferred List Available"); + wnm_deallocate_memory(wpa_s); + wpa_s->wnm_neighbor_report_elements = os_calloc( + WNM_MAX_NEIGHBOR_REPORT, + sizeof(struct neighbor_report)); + if (wpa_s->wnm_neighbor_report_elements == NULL) + return; + + while (pos + 2 <= end && + wpa_s->wnm_num_neighbor_report < WNM_MAX_NEIGHBOR_REPORT) + { + u8 tag = *pos++; + u8 len = *pos++; + + wpa_printf(MSG_DEBUG, "WNM: Neighbor report tag %u", + tag); + if (pos + len > end) { + wpa_printf(MSG_DEBUG, "WNM: Truncated request"); + return; + } + if (tag == WLAN_EID_NEIGHBOR_REPORT) { + struct neighbor_report *rep; + rep = &wpa_s->wnm_neighbor_report_elements[ + wpa_s->wnm_num_neighbor_report]; + wnm_parse_neighbor_report(wpa_s, pos, len, rep); + } + + pos += len; + wpa_s->wnm_num_neighbor_report++; + } + wnm_sort_cand_list(wpa_s); + wnm_dump_cand_list(wpa_s); + valid_ms = valid_int * beacon_int * 128 / 125; + wpa_printf(MSG_DEBUG, "WNM: Candidate list valid for %u ms", + valid_ms); + os_get_reltime(&wpa_s->wnm_cand_valid_until); + wpa_s->wnm_cand_valid_until.sec += valid_ms / 1000; + wpa_s->wnm_cand_valid_until.usec += (valid_ms % 1000) * 1000; + wpa_s->wnm_cand_valid_until.sec += + wpa_s->wnm_cand_valid_until.usec / 1000000; + wpa_s->wnm_cand_valid_until.usec %= 1000000; + os_memcpy(wpa_s->wnm_cand_from_bss, wpa_s->bssid, ETH_ALEN); + + if (wpa_s->last_scan_res_used > 0) { + struct os_reltime now; + + os_get_reltime(&now); + if (!os_reltime_expired(&now, &wpa_s->last_scan, 10)) { + wpa_printf(MSG_DEBUG, + "WNM: Try to use recent scan results"); + if (wnm_scan_process(wpa_s, 0) > 0) + return; + wpa_printf(MSG_DEBUG, + "WNM: No match in previous scan results - try a new scan"); + } + } + + wnm_set_scan_freqs(wpa_s); + wpa_supplicant_req_scan(wpa_s, 0, 0); + } else if (reply) { + enum bss_trans_mgmt_status_code status; + if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) + status = WNM_BSS_TM_ACCEPT; + else { + wpa_msg(wpa_s, MSG_INFO, "WNM: BSS Transition Management Request did not include candidates"); + status = WNM_BSS_TM_REJECT_UNSPECIFIED; + } + wnm_send_bss_transition_mgmt_resp(wpa_s, + wpa_s->wnm_dialog_token, + status, 0, NULL); + } +} + + +int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, + u8 query_reason) +{ + u8 buf[1000], *pos; + struct ieee80211_mgmt *mgmt; + size_t len; + int ret; + + wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Query to " + MACSTR " query_reason=%u", + MAC2STR(wpa_s->bssid), query_reason); + + mgmt = (struct ieee80211_mgmt *) buf; + os_memset(&buf, 0, sizeof(buf)); + os_memcpy(mgmt->da, wpa_s->bssid, ETH_ALEN); + os_memcpy(mgmt->sa, wpa_s->own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, wpa_s->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.bss_tm_query.action = WNM_BSS_TRANS_MGMT_QUERY; + mgmt->u.action.u.bss_tm_query.dialog_token = 1; + mgmt->u.action.u.bss_tm_query.query_reason = query_reason; + pos = mgmt->u.action.u.bss_tm_query.variable; + + len = pos - (u8 *) &mgmt->u.action.category; + + ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, + wpa_s->own_addr, wpa_s->bssid, + &mgmt->u.action.category, len, 0); + + return ret; +} + + +static void ieee802_11_rx_wnm_notif_req_wfa(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *data, + int len) +{ + const u8 *pos, *end, *next; + u8 ie, ie_len; + + pos = data; + end = data + len; + + while (pos + 1 < end) { + ie = *pos++; + ie_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: WFA subelement %u len %u", + ie, ie_len); + if (ie_len > end - pos) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for " + "subelement"); + break; + } + next = pos + ie_len; + if (ie_len < 4) { + pos = next; + continue; + } + wpa_printf(MSG_DEBUG, "WNM: Subelement OUI %06x type %u", + WPA_GET_BE24(pos), pos[3]); + +#ifdef CONFIG_HS20 + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_SUB_REM_NEEDED) { + /* Subscription Remediation subelement */ + const u8 *ie_end; + u8 url_len; + char *url; + u8 osu_method; + + wpa_printf(MSG_DEBUG, "WNM: Subscription Remediation " + "subelement"); + ie_end = pos + ie_len; + pos += 4; + url_len = *pos++; + if (url_len == 0) { + wpa_printf(MSG_DEBUG, "WNM: No Server URL included"); + url = NULL; + osu_method = 1; + } else { + if (pos + url_len + 1 > ie_end) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for Server URL (len=%u) and Server Method (left %d)", + url_len, + (int) (ie_end - pos)); + break; + } + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + osu_method = pos[url_len]; + } + hs20_rx_subscription_remediation(wpa_s, url, + osu_method); + os_free(url); + pos = next; + continue; + } + + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 8 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_DEAUTH_IMMINENT_NOTICE) { + const u8 *ie_end; + u8 url_len; + char *url; + u8 code; + u16 reauth_delay; + + ie_end = pos + ie_len; + pos += 4; + code = *pos++; + reauth_delay = WPA_GET_LE16(pos); + pos += 2; + url_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: HS 2.0 Deauthentication " + "Imminent - Reason Code %u " + "Re-Auth Delay %u URL Length %u", + code, reauth_delay, url_len); + if (pos + url_len > ie_end) + break; + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + hs20_rx_deauth_imminent_notice(wpa_s, code, + reauth_delay, url); + os_free(url); + pos = next; + continue; + } +#endif /* CONFIG_HS20 */ + + pos = next; + } +} + + +static void ieee802_11_rx_wnm_notif_req(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *frm, int len) +{ + const u8 *pos, *end; + u8 dialog_token, type; + + /* Dialog Token [1] | Type [1] | Subelements */ + + if (len < 2 || sa == NULL) + return; + end = frm + len; + pos = frm; + dialog_token = *pos++; + type = *pos++; + + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Received WNM-Notification Request " + "(dialog_token %u type %u sa " MACSTR ")", + dialog_token, type, MAC2STR(sa)); + wpa_hexdump(MSG_DEBUG, "WNM-Notification Request subelements", + pos, end - pos); + + if (wpa_s->wpa_state != WPA_COMPLETED || + os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) { + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: WNM-Notification frame not " + "from our AP - ignore it"); + return; + } + + switch (type) { + case 1: + ieee802_11_rx_wnm_notif_req_wfa(wpa_s, sa, pos, end - pos); + break; + default: + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Ignore unknown " + "WNM-Notification type %u", type); + break; + } +} + + +void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, + const struct ieee80211_mgmt *mgmt, size_t len) +{ + const u8 *pos, *end; + u8 act; + + if (len < IEEE80211_HDRLEN + 2) + return; + + pos = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 1; + act = *pos++; + end = ((const u8 *) mgmt) + len; + + wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR, + act, MAC2STR(mgmt->sa)); + if (wpa_s->wpa_state < WPA_ASSOCIATED || + os_memcmp(mgmt->sa, wpa_s->bssid, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "WNM: Ignore unexpected WNM Action " + "frame"); + return; + } + + switch (act) { + case WNM_BSS_TRANS_MGMT_REQ: + ieee802_11_rx_bss_trans_mgmt_req(wpa_s, pos, end, + !(mgmt->da[0] & 0x01)); + break; + case WNM_SLEEP_MODE_RESP: + ieee802_11_rx_wnmsleep_resp(wpa_s, pos, end - pos); + break; + case WNM_NOTIFICATION_REQ: + ieee802_11_rx_wnm_notif_req(wpa_s, mgmt->sa, pos, end - pos); + break; + default: + wpa_printf(MSG_ERROR, "WNM: Unknown request"); + break; + } +} -- cgit v1.2.3