ad08c3633c
This adds WPS support for both hostapd and wpa_supplicant. Both programs can be configured to act as WPS Enrollee and Registrar. Both PBC and PIN methods are supported. Currently, hostapd has more complete configuration option for WPS parameters and wpa_supplicant configuration style will likely change in the future. External Registrars are not yet supported in hostapd or wpa_supplicant. While wpa_supplicant has initial support for acting as an Registrar to configure an AP, this is still using number of hardcoded parameters which will need to be made configurable for proper operation.
2375 lines
61 KiB
C
2375 lines
61 KiB
C
/*
|
|
* hostapd - IEEE 802.11i-2004 / WPA Authenticator
|
|
* Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Alternatively, this software may be distributed under the terms of BSD
|
|
* license.
|
|
*
|
|
* See README and COPYING for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#ifndef CONFIG_NATIVE_WINDOWS
|
|
|
|
#include "common.h"
|
|
#include "config.h"
|
|
#include "eapol_sm.h"
|
|
#include "wpa.h"
|
|
#include "sha1.h"
|
|
#include "sha256.h"
|
|
#include "rc4.h"
|
|
#include "aes_wrap.h"
|
|
#include "crypto.h"
|
|
#include "eloop.h"
|
|
#include "ieee802_11.h"
|
|
#include "pmksa_cache.h"
|
|
#include "state_machine.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 void wpa_sm_step(struct wpa_state_machine *sm);
|
|
static int wpa_verify_key_mic(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);
|
|
|
|
/* Default timeouts are 100 ms, but this seems to be a bit too fast for most
|
|
* WPA Supplicants, so use a bit longer timeout. */
|
|
static const u32 dot11RSNAConfigGroupUpdateTimeOut = 1000; /* ms */
|
|
static const u32 dot11RSNAConfigGroupUpdateCount = 3;
|
|
static const u32 dot11RSNAConfigPairwiseUpdateTimeOut = 1000; /* ms */
|
|
static const u32 dot11RSNAConfigPairwiseUpdateCount = 3;
|
|
|
|
/* TODO: make these configurable */
|
|
static const int dot11RSNAConfigPMKLifetime = 43200;
|
|
static const int dot11RSNAConfigPMKReauthThreshold = 70;
|
|
static const int dot11RSNAConfigSATimeout = 60;
|
|
|
|
|
|
static inline void wpa_auth_mic_failure_report(
|
|
struct wpa_authenticator *wpa_auth, const u8 *addr)
|
|
{
|
|
if (wpa_auth->cb.mic_failure_report)
|
|
wpa_auth->cb.mic_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 *prev_psk)
|
|
{
|
|
if (wpa_auth->cb.get_psk == NULL)
|
|
return NULL;
|
|
return wpa_auth->cb.get_psk(wpa_auth->cb.ctx, 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,
|
|
const char *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_get_seqnum_igtk(struct wpa_authenticator *wpa_auth,
|
|
const u8 *addr, int idx, u8 *seq)
|
|
{
|
|
if (wpa_auth->cb.get_seqnum_igtk == NULL)
|
|
return -1;
|
|
return wpa_auth->cb.get_seqnum_igtk(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);
|
|
}
|
|
|
|
|
|
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_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 */
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void wpa_rekey_gmk(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_authenticator *wpa_auth = eloop_ctx;
|
|
|
|
if (os_get_random(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");
|
|
}
|
|
|
|
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;
|
|
|
|
wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "rekeying GTK");
|
|
for (group = wpa_auth->group; group; group = group->next) {
|
|
group->GTKReKey = TRUE;
|
|
do {
|
|
group->changed = FALSE;
|
|
wpa_group_sm_step(wpa_auth, group);
|
|
} while (group->changed);
|
|
}
|
|
|
|
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 struct wpa_group * wpa_group_init(struct wpa_authenticator *wpa_auth,
|
|
int vlan_id)
|
|
{
|
|
struct wpa_group *group;
|
|
u8 buf[ETH_ALEN + 8 + sizeof(group)];
|
|
u8 rkey[32];
|
|
|
|
group = os_zalloc(sizeof(struct wpa_group));
|
|
if (group == NULL)
|
|
return NULL;
|
|
|
|
group->GTKAuthenticator = TRUE;
|
|
group->vlan_id = vlan_id;
|
|
|
|
switch (wpa_auth->conf.wpa_group) {
|
|
case WPA_CIPHER_CCMP:
|
|
group->GTK_len = 16;
|
|
break;
|
|
case WPA_CIPHER_TKIP:
|
|
group->GTK_len = 32;
|
|
break;
|
|
case WPA_CIPHER_WEP104:
|
|
group->GTK_len = 13;
|
|
break;
|
|
case WPA_CIPHER_WEP40:
|
|
group->GTK_len = 5;
|
|
break;
|
|
}
|
|
|
|
/* 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);
|
|
os_memcpy(buf + ETH_ALEN + 8, &group, sizeof(group));
|
|
if (os_get_random(rkey, sizeof(rkey)) ||
|
|
os_get_random(group->GMK, WPA_GMK_LEN)) {
|
|
wpa_printf(MSG_ERROR, "Failed to get random data for WPA "
|
|
"initialization.");
|
|
os_free(group);
|
|
return NULL;
|
|
}
|
|
|
|
sha1_prf(rkey, sizeof(rkey), "Init Counter", buf, sizeof(buf),
|
|
group->Counter, WPA_NONCE_LEN);
|
|
|
|
group->GInit = TRUE;
|
|
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
|
|
* 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);
|
|
if (wpa_auth->group == NULL) {
|
|
os_free(wpa_auth->wpa_ie);
|
|
os_free(wpa_auth);
|
|
return NULL;
|
|
}
|
|
|
|
wpa_auth->pmksa = pmksa_cache_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->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->wpa_ie);
|
|
pmksa_cache_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);
|
|
}
|
|
|
|
return wpa_auth;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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_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 */
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct wpa_state_machine *
|
|
wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr)
|
|
{
|
|
struct wpa_state_machine *sm;
|
|
|
|
sm = os_zalloc(sizeof(struct wpa_state_machine));
|
|
if (sm == NULL)
|
|
return NULL;
|
|
os_memcpy(sm->addr, addr, ETH_ALEN);
|
|
|
|
sm->wpa_auth = wpa_auth;
|
|
sm->group = wpa_auth->group;
|
|
|
|
return sm;
|
|
}
|
|
|
|
|
|
void 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;
|
|
|
|
#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");
|
|
return;
|
|
}
|
|
#endif /* CONFIG_IEEE80211R */
|
|
|
|
if (sm->started) {
|
|
os_memset(sm->key_replay_counter, 0, WPA_REPLAY_COUNTER_LEN);
|
|
sm->ReAuthenticationRequest = TRUE;
|
|
wpa_sm_step(sm);
|
|
return;
|
|
}
|
|
|
|
wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG,
|
|
"start authentication");
|
|
sm->started = 1;
|
|
|
|
sm->Init = TRUE;
|
|
wpa_sm_step(sm);
|
|
sm->Init = FALSE;
|
|
sm->AuthenticationRequest = TRUE;
|
|
wpa_sm_step(sm);
|
|
}
|
|
|
|
|
|
static void wpa_free_sta_sm(struct wpa_state_machine *sm)
|
|
{
|
|
os_free(sm->last_rx_eapol_key);
|
|
os_free(sm->wpa_ie);
|
|
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);
|
|
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;
|
|
}
|
|
|
|
|
|
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;
|
|
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;
|
|
|
|
if (wpa_auth == NULL || !wpa_auth->conf.wpa || sm == NULL)
|
|
return;
|
|
|
|
if (data_len < sizeof(*hdr) + sizeof(*key))
|
|
return;
|
|
|
|
hdr = (struct ieee802_1x_hdr *) data;
|
|
key = (struct wpa_eapol_key *) (hdr + 1);
|
|
key_info = WPA_GET_BE16(key->key_info);
|
|
key_data_length = WPA_GET_BE16(key->key_data_length);
|
|
if (key_data_length > data_len - sizeof(*hdr) - sizeof(*key)) {
|
|
wpa_printf(MSG_INFO, "WPA: Invalid EAPOL-Key frame - "
|
|
"key_data overflow (%d > %lu)",
|
|
key_data_length,
|
|
(unsigned long) (data_len - sizeof(*hdr) -
|
|
sizeof(*key)));
|
|
return;
|
|
}
|
|
|
|
/* 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) {
|
|
if (wpa_use_aes_cmac(sm) &&
|
|
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");
|
|
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) &&
|
|
(!sm->key_replay_counter_valid ||
|
|
os_memcmp(key->replay_counter, sm->key_replay_counter,
|
|
WPA_REPLAY_COUNTER_LEN) != 0)) {
|
|
wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO,
|
|
"received EAPOL-Key %s with unexpected "
|
|
"replay counter", msgtxt);
|
|
wpa_hexdump(MSG_DEBUG, "expected replay counter",
|
|
sm->key_replay_counter, WPA_REPLAY_COUNTER_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "received replay counter",
|
|
key->replay_counter, WPA_REPLAY_COUNTER_LEN);
|
|
return;
|
|
}
|
|
|
|
switch (msg) {
|
|
case PAIRWISE_2:
|
|
if (sm->wpa_ptk_state != WPA_PTK_PTKSTART &&
|
|
sm->wpa_ptk_state != WPA_PTK_PTKCALCNEGOTIATING) {
|
|
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;
|
|
}
|
|
if (sm->wpa_ie == NULL ||
|
|
sm->wpa_ie_len != key_data_length ||
|
|
os_memcmp(sm->wpa_ie, key + 1, key_data_length) != 0) {
|
|
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",
|
|
(u8 *) (key + 1), key_data_length);
|
|
/* MLME-DEAUTHENTICATE.request */
|
|
wpa_sta_disconnect(wpa_auth, sm->addr);
|
|
return;
|
|
}
|
|
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) {
|
|
if (wpa_verify_key_mic(&sm->PTK, 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);
|
|
}
|
|
|
|
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);
|
|
#endif /* CONFIG_PEERKEY */
|
|
return;
|
|
} else if (key_info & WPA_KEY_INFO_ERROR) {
|
|
/* Supplicant reported a Michael MIC error */
|
|
wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
|
|
"received EAPOL-Key Error Request "
|
|
"(STA detected Michael MIC failure)");
|
|
wpa_auth_mic_failure_report(wpa_auth, sm->addr);
|
|
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);
|
|
} 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);
|
|
#endif /* CONFIG_PEERKEY */
|
|
} else if (key_data_length > 0 &&
|
|
wpa_parse_kde_ies((const u8 *) (key + 1),
|
|
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");
|
|
/* FIX: why was this triggering PTK rekeying for the
|
|
* STA that requested Group Key rekeying?? */
|
|
/* wpa_request_new_ptk(sta->wpa_sm); */
|
|
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. */
|
|
sm->key_replay_counter_valid = FALSE;
|
|
}
|
|
|
|
#ifdef CONFIG_PEERKEY
|
|
if (msg == SMK_M3) {
|
|
wpa_smk_m3(wpa_auth, sm, key);
|
|
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->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 void wpa_gmk_to_gtk(const u8 *gmk, const u8 *addr, const u8 *gnonce,
|
|
u8 *gtk, size_t gtk_len)
|
|
{
|
|
u8 data[ETH_ALEN + WPA_NONCE_LEN];
|
|
|
|
/* GTK = PRF-X(GMK, "Group key expansion", AA || GNonce) */
|
|
os_memcpy(data, addr, ETH_ALEN);
|
|
os_memcpy(data + ETH_ALEN, gnonce, WPA_NONCE_LEN);
|
|
|
|
#ifdef CONFIG_IEEE80211W
|
|
sha256_prf(gmk, WPA_GMK_LEN, "Group key expansion",
|
|
data, sizeof(data), gtk, gtk_len);
|
|
#else /* CONFIG_IEEE80211W */
|
|
sha1_prf(gmk, WPA_GMK_LEN, "Group key expansion",
|
|
data, sizeof(data), gtk, gtk_len);
|
|
#endif /* CONFIG_IEEE80211W */
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "GMK", gmk, WPA_GMK_LEN);
|
|
wpa_hexdump_key(MSG_DEBUG, "GTK", gtk, gtk_len);
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
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;
|
|
size_t len;
|
|
int alg;
|
|
int key_data_len, pad_len = 0;
|
|
u8 *buf, *pos;
|
|
int version, pairwise;
|
|
|
|
len = sizeof(struct ieee802_1x_hdr) + sizeof(struct wpa_eapol_key);
|
|
|
|
if (force_version)
|
|
version = force_version;
|
|
else if (wpa_use_aes_cmac(sm))
|
|
version = WPA_KEY_INFO_TYPE_AES_128_CMAC;
|
|
else if (sm->pairwise == WPA_CIPHER_CCMP)
|
|
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 ||
|
|
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);
|
|
|
|
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;
|
|
switch (alg) {
|
|
case WPA_CIPHER_CCMP:
|
|
WPA_PUT_BE16(key->key_length, 16);
|
|
break;
|
|
case WPA_CIPHER_TKIP:
|
|
WPA_PUT_BE16(key->key_length, 32);
|
|
break;
|
|
case WPA_CIPHER_WEP40:
|
|
WPA_PUT_BE16(key->key_length, 5);
|
|
break;
|
|
case WPA_CIPHER_WEP104:
|
|
WPA_PUT_BE16(key->key_length, 13);
|
|
break;
|
|
}
|
|
if (key_info & WPA_KEY_INFO_SMK_MESSAGE)
|
|
WPA_PUT_BE16(key->key_length, 0);
|
|
|
|
/* FIX: STSL: what to use as key_replay_counter? */
|
|
inc_byte_array(sm->key_replay_counter, WPA_REPLAY_COUNTER_LEN);
|
|
os_memcpy(key->replay_counter, sm->key_replay_counter,
|
|
WPA_REPLAY_COUNTER_LEN);
|
|
sm->key_replay_counter_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 + 1, kde, kde_len);
|
|
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 ||
|
|
version == WPA_KEY_INFO_TYPE_AES_128_CMAC) {
|
|
if (aes_wrap(sm->PTK.kek, (key_data_len - 8) / 8, buf,
|
|
(u8 *) (key + 1))) {
|
|
os_free(hdr);
|
|
os_free(buf);
|
|
return;
|
|
}
|
|
WPA_PUT_BE16(key->key_data_length, key_data_len);
|
|
} else {
|
|
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, 16);
|
|
os_memcpy(key + 1, buf, key_data_len);
|
|
rc4_skip(ek, 32, 256, (u8 *) (key + 1), key_data_len);
|
|
WPA_PUT_BE16(key->key_data_length, key_data_len);
|
|
}
|
|
os_free(buf);
|
|
}
|
|
|
|
if (key_info & WPA_KEY_INFO_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;
|
|
}
|
|
wpa_eapol_key_mic(sm->PTK.kck, version, (u8 *) hdr, len,
|
|
key->key_mic);
|
|
}
|
|
|
|
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;
|
|
|
|
if (sm == NULL)
|
|
return;
|
|
|
|
__wpa_send_eapol(wpa_auth, sm, key_info, key_rsc, nonce, kde, kde_len,
|
|
keyidx, encr, 0);
|
|
|
|
timeout_ms = pairwise ? dot11RSNAConfigPairwiseUpdateTimeOut :
|
|
dot11RSNAConfigGroupUpdateTimeOut;
|
|
eloop_register_timeout(timeout_ms / 1000, (timeout_ms % 1000) * 1000,
|
|
wpa_send_eapol_timeout, wpa_auth, sm);
|
|
}
|
|
|
|
|
|
static int wpa_verify_key_mic(struct wpa_ptk *PTK, u8 *data, size_t data_len)
|
|
{
|
|
struct ieee802_1x_hdr *hdr;
|
|
struct wpa_eapol_key *key;
|
|
u16 key_info;
|
|
int ret = 0;
|
|
u8 mic[16];
|
|
|
|
if (data_len < sizeof(*hdr) + sizeof(*key))
|
|
return -1;
|
|
|
|
hdr = (struct ieee802_1x_hdr *) data;
|
|
key = (struct wpa_eapol_key *) (hdr + 1);
|
|
key_info = WPA_GET_BE16(key->key_info);
|
|
os_memcpy(mic, key->key_mic, 16);
|
|
os_memset(key->key_mic, 0, 16);
|
|
if (wpa_eapol_key_mic(PTK->kck, key_info & WPA_KEY_INFO_TYPE_MASK,
|
|
data, data_len, key->key_mic) ||
|
|
os_memcmp(mic, key->key_mic, 16) != 0)
|
|
ret = -1;
|
|
os_memcpy(key->key_mic, mic, 16);
|
|
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, "none", sm->addr, 0, (u8 *) "", 0);
|
|
sm->pairwise_set = FALSE;
|
|
eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm);
|
|
}
|
|
|
|
|
|
void wpa_auth_sm_event(struct wpa_state_machine *sm, wpa_event event)
|
|
{
|
|
int remove_ptk = 1;
|
|
|
|
if (sm == NULL)
|
|
return;
|
|
|
|
wpa_auth_vlogger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
|
|
"event %d notification", event);
|
|
|
|
switch (event) {
|
|
case WPA_AUTH:
|
|
case WPA_ASSOC:
|
|
break;
|
|
case WPA_DEAUTH:
|
|
case WPA_DISASSOC:
|
|
sm->DeauthenticationRequest = TRUE;
|
|
break;
|
|
case WPA_REAUTH:
|
|
case WPA_REAUTH_EAPOL:
|
|
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
|
|
/* Using FT protocol, not WPA auth state machine */
|
|
sm->ft_completed = 1;
|
|
return;
|
|
#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);
|
|
}
|
|
|
|
wpa_sm_step(sm);
|
|
}
|
|
|
|
|
|
static const char * wpa_alg_txt(int alg)
|
|
{
|
|
switch (alg) {
|
|
case WPA_CIPHER_CCMP:
|
|
return "CCMP";
|
|
case WPA_CIPHER_TKIP:
|
|
return "TKIP";
|
|
case WPA_CIPHER_WEP104:
|
|
case WPA_CIPHER_WEP40:
|
|
return "WEP";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
SM_STATE(WPA_PTK, AUTHENTICATION2)
|
|
{
|
|
SM_ENTRY_MA(WPA_PTK, AUTHENTICATION2, wpa_ptk);
|
|
os_memcpy(sm->ANonce, sm->group->Counter, WPA_NONCE_LEN);
|
|
inc_byte_array(sm->group->Counter, WPA_NONCE_LEN);
|
|
sm->ReAuthenticationRequest = FALSE;
|
|
/* 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");
|
|
}
|
|
|
|
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, 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;
|
|
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)) {
|
|
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 {
|
|
/*
|
|
* 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);
|
|
sm->TimeoutCtr++;
|
|
}
|
|
|
|
|
|
static int wpa_derive_ptk(struct wpa_state_machine *sm, 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 */
|
|
|
|
wpa_pmk_to_ptk(pmk, PMK_LEN, "Pairwise key expansion",
|
|
sm->wpa_auth->addr, sm->addr, sm->ANonce, sm->SNonce,
|
|
(u8 *) ptk, sizeof(*ptk),
|
|
wpa_key_mgmt_sha256(sm->wpa_key_mgmt));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
|
|
{
|
|
struct wpa_ptk PTK;
|
|
int ok = 0;
|
|
const u8 *pmk = NULL;
|
|
|
|
SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING, wpa_ptk);
|
|
sm->EAPOLKeyReceived = 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, pmk);
|
|
if (pmk == NULL)
|
|
break;
|
|
} else
|
|
pmk = sm->PMK;
|
|
|
|
wpa_derive_ptk(sm, pmk, &PTK);
|
|
|
|
if (wpa_verify_key_mic(&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");
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
return 2 + RSN_SELECTOR_LEN + sizeof(struct wpa_igtk_kde);
|
|
}
|
|
|
|
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;
|
|
|
|
if (!sm->mgmt_frame_prot)
|
|
return pos;
|
|
|
|
igtk.keyid[0] = gsm->GN_igtk;
|
|
igtk.keyid[1] = 0;
|
|
if (wpa_auth_get_seqnum_igtk(sm->wpa_auth, NULL, gsm->GN_igtk, igtk.pn)
|
|
< 0)
|
|
os_memset(igtk.pn, 0, sizeof(igtk.pn));
|
|
os_memcpy(igtk.igtk, gsm->IGTK[gsm->GN_igtk - 4], WPA_IGTK_LEN);
|
|
pos = wpa_add_kde(pos, RSN_KEY_DATA_IGTK,
|
|
(const u8 *) &igtk, sizeof(igtk), 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;
|
|
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;
|
|
/* Send EAPOL(1, 1, 1, Pair, P, RSC, ANonce, MIC(PTK), RSNIE, GTK[GN])
|
|
*/
|
|
os_memset(rsc, 0, WPA_KEY_RSC_LEN);
|
|
wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, rsc);
|
|
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 */
|
|
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;
|
|
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;
|
|
}
|
|
|
|
kde_len = wpa_ie_len + ieee80211w_kde_len(sm);
|
|
if (gtk)
|
|
kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len;
|
|
kde = os_malloc(kde_len);
|
|
if (kde == NULL)
|
|
return;
|
|
|
|
pos = kde;
|
|
os_memcpy(pos, wpa_ie, wpa_ie_len);
|
|
pos += wpa_ie_len;
|
|
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);
|
|
|
|
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->TimeoutCtr++;
|
|
}
|
|
|
|
|
|
SM_STATE(WPA_PTK, PTKINITDONE)
|
|
{
|
|
SM_ENTRY_MA(WPA_PTK, PTKINITDONE, wpa_ptk);
|
|
sm->EAPOLKeyReceived = FALSE;
|
|
if (sm->Pair) {
|
|
char *alg;
|
|
int klen;
|
|
if (sm->pairwise == WPA_CIPHER_TKIP) {
|
|
alg = "TKIP";
|
|
klen = 32;
|
|
} else {
|
|
alg = "CCMP";
|
|
klen = 16;
|
|
}
|
|
if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0,
|
|
sm->PTK.tk1, 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 */)
|
|
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++;
|
|
SM_ENTER(WPA_PTK, DISCONNECT);
|
|
}
|
|
break;
|
|
case WPA_PTK_INITPSK:
|
|
if (wpa_auth_get_psk(sm->wpa_auth, sm->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++;
|
|
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->EAPOLKeyReceived && !sm->EAPOLKeyRequest &&
|
|
sm->EAPOLKeyPairwise && sm->MICVerified)
|
|
SM_ENTER(WPA_PTK, PTKINITDONE);
|
|
else if (sm->TimeoutCtr >
|
|
(int) dot11RSNAConfigPairwiseUpdateCount) {
|
|
wpa_auth->dot11RSNA4WayHandshakeFailures++;
|
|
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;
|
|
u8 *kde, *pos, hdr[2];
|
|
size_t kde_len;
|
|
|
|
SM_ENTRY_MA(WPA_PTK_GROUP, REKEYNEGOTIATING, wpa_ptk_group);
|
|
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");
|
|
|
|
if (sm->wpa == WPA_VERSION_WPA2) {
|
|
kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len +
|
|
ieee80211w_kde_len(sm);
|
|
kde = os_malloc(kde_len);
|
|
if (kde == NULL)
|
|
return;
|
|
|
|
pos = kde;
|
|
hdr[0] = gsm->GN & 0x03;
|
|
hdr[1] = 0;
|
|
pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2,
|
|
gsm->GTK[gsm->GN - 1], gsm->GTK_len);
|
|
pos = ieee80211w_kde_add(sm, pos);
|
|
} else {
|
|
kde = gsm->GTK[gsm->GN - 1];
|
|
pos = kde + 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, pos - kde, gsm->GN, 1);
|
|
if (sm->wpa == WPA_VERSION_WPA2)
|
|
os_free(kde);
|
|
sm->GTimeoutCtr++;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
/* FIX: is this the correct way of getting GNonce? */
|
|
os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN);
|
|
inc_byte_array(group->Counter, WPA_NONCE_LEN);
|
|
wpa_gmk_to_gtk(group->GMK, wpa_auth->addr, group->GNonce,
|
|
group->GTK[group->GN - 1], group->GTK_len);
|
|
|
|
#ifdef CONFIG_IEEE80211W
|
|
if (wpa_auth->conf.ieee80211w != WPA_NO_IEEE80211W) {
|
|
if (os_get_random(group->IGTK[group->GN_igtk - 4],
|
|
WPA_IGTK_LEN) < 0) {
|
|
wpa_printf(MSG_INFO, "RSN: Failed to get new random "
|
|
"IGTK");
|
|
ret = -1;
|
|
}
|
|
wpa_hexdump_key(MSG_DEBUG, "IGTK",
|
|
group->IGTK[group->GN_igtk - 4], WPA_IGTK_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 (sm->wpa_ptk_state != WPA_PTK_PTKINITDONE) {
|
|
wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
|
|
"Not in PTKINITDONE; skip Group Key update");
|
|
return 0;
|
|
}
|
|
if (sm->GUpdateStationKeys) {
|
|
/*
|
|
* This should not really happen, but just in case, make sure
|
|
* we do not count the same STA twice in GKeyDoneStations.
|
|
*/
|
|
wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
|
|
"GUpdateStationKeys already set - do not "
|
|
"increment GKeyDoneStations");
|
|
} else {
|
|
sm->group->GKeyDoneStations++;
|
|
sm->GUpdateStationKeys = TRUE;
|
|
}
|
|
wpa_sm_step(sm);
|
|
return 0;
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
wpa_auth_for_each_sta(wpa_auth, wpa_group_update_sta, NULL);
|
|
wpa_printf(MSG_DEBUG, "wpa_group_setkeys: GKeyDoneStations=%d",
|
|
group->GKeyDoneStations);
|
|
}
|
|
|
|
|
|
static void 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;
|
|
wpa_auth_set_key(wpa_auth, group->vlan_id,
|
|
wpa_alg_txt(wpa_auth->conf.wpa_group),
|
|
NULL, group->GN, group->GTK[group->GN - 1],
|
|
group->GTK_len);
|
|
|
|
#ifdef CONFIG_IEEE80211W
|
|
if (wpa_auth->conf.ieee80211w != WPA_NO_IEEE80211W) {
|
|
wpa_auth_set_key(wpa_auth, group->vlan_id, "IGTK",
|
|
NULL, group->GN_igtk,
|
|
group->IGTK[group->GN_igtk - 4],
|
|
WPA_IGTK_LEN);
|
|
}
|
|
#endif /* CONFIG_IEEE80211W */
|
|
}
|
|
|
|
|
|
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_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 void wpa_sm_step(struct wpa_state_machine *sm)
|
|
{
|
|
if (sm == NULL)
|
|
return;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
static const char * wpa_bool_txt(int bool)
|
|
{
|
|
return bool ? "TRUE" : "FALSE";
|
|
}
|
|
|
|
|
|
static int wpa_cipher_bits(int cipher)
|
|
{
|
|
switch (cipher) {
|
|
case WPA_CIPHER_CCMP:
|
|
return 128;
|
|
case WPA_CIPHER_TKIP:
|
|
return 256;
|
|
case WPA_CIPHER_WEP104:
|
|
return 104;
|
|
case WPA_CIPHER_WEP40:
|
|
return 40;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
#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];
|
|
|
|
if (wpa_auth == NULL)
|
|
return len;
|
|
|
|
ret = os_snprintf(buf + len, buflen - len,
|
|
"dot11RSNAOptionImplemented=TRUE\n"
|
|
#ifdef CONFIG_RSN_PREAUTH
|
|
"dot11RSNAPreauthenticationImplemented=TRUE\n"
|
|
#else /* CONFIG_RSN_PREAUTH */
|
|
"dot11RSNAPreauthenticationImplemented=FALSE\n"
|
|
#endif /* CONFIG_RSN_PREAUTH */
|
|
"dot11RSNAEnabled=%s\n"
|
|
"dot11RSNAPreauthenticationEnabled=%s\n",
|
|
wpa_bool_txt(wpa_auth->conf.wpa & WPA_PROTO_RSN),
|
|
wpa_bool_txt(wpa_auth->conf.rsn_preauth));
|
|
if (ret < 0 || (size_t) ret >= buflen - len)
|
|
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_bits(wpa_auth->conf.wpa_group),
|
|
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 (ret < 0 || (size_t) ret >= buflen - len)
|
|
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 (ret < 0 || (size_t) ret >= buflen - len)
|
|
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 */
|
|
|
|
if (sm->wpa == WPA_VERSION_WPA) {
|
|
if (sm->pairwise == WPA_CIPHER_CCMP)
|
|
pairwise = WPA_CIPHER_SUITE_CCMP;
|
|
else if (sm->pairwise == WPA_CIPHER_TKIP)
|
|
pairwise = WPA_CIPHER_SUITE_TKIP;
|
|
else if (sm->pairwise == WPA_CIPHER_WEP104)
|
|
pairwise = WPA_CIPHER_SUITE_WEP104;
|
|
else if (sm->pairwise == WPA_CIPHER_WEP40)
|
|
pairwise = WPA_CIPHER_SUITE_WEP40;
|
|
else if (sm->pairwise == WPA_CIPHER_NONE)
|
|
pairwise = WPA_CIPHER_SUITE_NONE;
|
|
} else if (sm->wpa == WPA_VERSION_WPA2) {
|
|
if (sm->pairwise == WPA_CIPHER_CCMP)
|
|
pairwise = RSN_CIPHER_SUITE_CCMP;
|
|
else if (sm->pairwise == WPA_CIPHER_TKIP)
|
|
pairwise = RSN_CIPHER_SUITE_TKIP;
|
|
else if (sm->pairwise == WPA_CIPHER_WEP104)
|
|
pairwise = RSN_CIPHER_SUITE_WEP104;
|
|
else if (sm->pairwise == WPA_CIPHER_WEP40)
|
|
pairwise = RSN_CIPHER_SUITE_WEP40;
|
|
else if (sm->pairwise == WPA_CIPHER_NONE)
|
|
pairwise = RSN_CIPHER_SUITE_NONE;
|
|
} else
|
|
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"
|
|
"dot11RSNAStatsTKIPRemoveMICFailures=%u\n"
|
|
/* TODO: dot11RSNAStatsCCMPReplays */
|
|
/* TODO: dot11RSNAStatsCCMPDecryptErrors */
|
|
/* TODO: dot11RSNAStatsTKIPReplays */,
|
|
MAC2STR(sm->addr),
|
|
RSN_SUITE_ARG(pairwise),
|
|
sm->dot11RSNAStatsTKIPLocalMICFailures,
|
|
sm->dot11RSNAStatsTKIPRemoteMICFailures);
|
|
if (ret < 0 || (size_t) ret >= buflen - len)
|
|
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 (ret < 0 || (size_t) ret >= buflen - len)
|
|
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_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)
|
|
return -1;
|
|
|
|
if (pmksa_cache_add(sm->wpa_auth->pmksa, pmk, PMK_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_add(wpa_auth->pmksa, pmk, len, wpa_auth->addr,
|
|
sta_addr, session_timeout, eapol,
|
|
WPA_KEY_MGMT_IEEE8021X))
|
|
return 0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
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);
|
|
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;
|
|
|
|
wpa_printf(MSG_DEBUG, "WPA: Moving STA " MACSTR " to use group state "
|
|
"machine for VLAN ID %d", MAC2STR(sm->addr), vlan_id);
|
|
|
|
sm->group = group;
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_NATIVE_WINDOWS */
|