blob: 09cf4f6b922256d93f26af9e4aa3bf610cd5b41f [file] [log] [blame]
/*
* EAPOL supplicant state machines
* Copyright (c) 2004-2012, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "common.h"
#include "state_machine.h"
#include "wpabuf.h"
#include "eloop.h"
#include "crypto/crypto.h"
#include "crypto/md5.h"
#include "common/eapol_common.h"
#include "eap_peer/eap.h"
#include "eap_peer/eap_proxy.h"
#include "eapol_supp_sm.h"
#define STATE_MACHINE_DATA struct eapol_sm
#define STATE_MACHINE_DEBUG_PREFIX "EAPOL"
/* IEEE 802.1X-2004 - Supplicant - EAPOL state machines */
/**
* struct eapol_sm - Internal data for EAPOL state machines
*/
struct eapol_sm {
/* Timers */
unsigned int authWhile;
unsigned int heldWhile;
unsigned int startWhen;
unsigned int idleWhile; /* for EAP state machine */
int timer_tick_enabled;
/* Global variables */
Boolean eapFail;
Boolean eapolEap;
Boolean eapSuccess;
Boolean initialize;
Boolean keyDone;
Boolean keyRun;
PortControl portControl;
Boolean portEnabled;
PortStatus suppPortStatus; /* dot1xSuppControlledPortStatus */
Boolean portValid;
Boolean suppAbort;
Boolean suppFail;
Boolean suppStart;
Boolean suppSuccess;
Boolean suppTimeout;
/* Supplicant PAE state machine */
enum {
SUPP_PAE_UNKNOWN = 0,
SUPP_PAE_DISCONNECTED = 1,
SUPP_PAE_LOGOFF = 2,
SUPP_PAE_CONNECTING = 3,
SUPP_PAE_AUTHENTICATING = 4,
SUPP_PAE_AUTHENTICATED = 5,
/* unused(6) */
SUPP_PAE_HELD = 7,
SUPP_PAE_RESTART = 8,
SUPP_PAE_S_FORCE_AUTH = 9,
SUPP_PAE_S_FORCE_UNAUTH = 10
} SUPP_PAE_state; /* dot1xSuppPaeState */
/* Variables */
Boolean userLogoff;
Boolean logoffSent;
unsigned int startCount;
Boolean eapRestart;
PortControl sPortMode;
/* Constants */
unsigned int heldPeriod; /* dot1xSuppHeldPeriod */
unsigned int startPeriod; /* dot1xSuppStartPeriod */
unsigned int maxStart; /* dot1xSuppMaxStart */
/* Key Receive state machine */
enum {
KEY_RX_UNKNOWN = 0,
KEY_RX_NO_KEY_RECEIVE, KEY_RX_KEY_RECEIVE
} KEY_RX_state;
/* Variables */
Boolean rxKey;
/* Supplicant Backend state machine */
enum {
SUPP_BE_UNKNOWN = 0,
SUPP_BE_INITIALIZE = 1,
SUPP_BE_IDLE = 2,
SUPP_BE_REQUEST = 3,
SUPP_BE_RECEIVE = 4,
SUPP_BE_RESPONSE = 5,
SUPP_BE_FAIL = 6,
SUPP_BE_TIMEOUT = 7,
SUPP_BE_SUCCESS = 8
} SUPP_BE_state; /* dot1xSuppBackendPaeState */
/* Variables */
Boolean eapNoResp;
Boolean eapReq;
Boolean eapResp;
/* Constants */
unsigned int authPeriod; /* dot1xSuppAuthPeriod */
/* Statistics */
unsigned int dot1xSuppEapolFramesRx;
unsigned int dot1xSuppEapolFramesTx;
unsigned int dot1xSuppEapolStartFramesTx;
unsigned int dot1xSuppEapolLogoffFramesTx;
unsigned int dot1xSuppEapolRespFramesTx;
unsigned int dot1xSuppEapolReqIdFramesRx;
unsigned int dot1xSuppEapolReqFramesRx;
unsigned int dot1xSuppInvalidEapolFramesRx;
unsigned int dot1xSuppEapLengthErrorFramesRx;
unsigned int dot1xSuppLastEapolFrameVersion;
unsigned char dot1xSuppLastEapolFrameSource[6];
/* Miscellaneous variables (not defined in IEEE 802.1X-2004) */
Boolean changed;
struct eap_sm *eap;
struct eap_peer_config *config;
Boolean initial_req;
u8 *last_rx_key;
size_t last_rx_key_len;
struct wpabuf *eapReqData; /* for EAP */
Boolean altAccept; /* for EAP */
Boolean altReject; /* for EAP */
Boolean eapTriggerStart;
Boolean replay_counter_valid;
u8 last_replay_counter[16];
struct eapol_config conf;
struct eapol_ctx *ctx;
enum { EAPOL_CB_IN_PROGRESS = 0, EAPOL_CB_SUCCESS, EAPOL_CB_FAILURE }
cb_status;
Boolean cached_pmk;
Boolean unicast_key_received, broadcast_key_received;
Boolean force_authorized_update;
#ifdef CONFIG_EAP_PROXY
Boolean use_eap_proxy;
struct eap_proxy_sm *eap_proxy;
#endif /* CONFIG_EAP_PROXY */
};
static void eapol_sm_txLogoff(struct eapol_sm *sm);
static void eapol_sm_txStart(struct eapol_sm *sm);
static void eapol_sm_processKey(struct eapol_sm *sm);
static void eapol_sm_getSuppRsp(struct eapol_sm *sm);
static void eapol_sm_txSuppRsp(struct eapol_sm *sm);
static void eapol_sm_abortSupp(struct eapol_sm *sm);
static void eapol_sm_abort_cached(struct eapol_sm *sm);
static void eapol_sm_step_timeout(void *eloop_ctx, void *timeout_ctx);
static void eapol_sm_set_port_authorized(struct eapol_sm *sm);
static void eapol_sm_set_port_unauthorized(struct eapol_sm *sm);
/* Port Timers state machine - implemented as a function that will be called
* once a second as a registered event loop timeout */
static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx)
{
struct eapol_sm *sm = timeout_ctx;
if (sm->authWhile > 0) {
sm->authWhile--;
if (sm->authWhile == 0)
wpa_printf(MSG_DEBUG, "EAPOL: authWhile --> 0");
}
if (sm->heldWhile > 0) {
sm->heldWhile--;
if (sm->heldWhile == 0)
wpa_printf(MSG_DEBUG, "EAPOL: heldWhile --> 0");
}
if (sm->startWhen > 0) {
sm->startWhen--;
if (sm->startWhen == 0)
wpa_printf(MSG_DEBUG, "EAPOL: startWhen --> 0");
}
if (sm->idleWhile > 0) {
sm->idleWhile--;
if (sm->idleWhile == 0)
wpa_printf(MSG_DEBUG, "EAPOL: idleWhile --> 0");
}
if (sm->authWhile | sm->heldWhile | sm->startWhen | sm->idleWhile) {
eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx,
sm);
} else {
wpa_printf(MSG_DEBUG, "EAPOL: disable timer tick");
sm->timer_tick_enabled = 0;
}
eapol_sm_step(sm);
}
static void eapol_enable_timer_tick(struct eapol_sm *sm)
{
if (sm->timer_tick_enabled)
return;
wpa_printf(MSG_DEBUG, "EAPOL: enable timer tick");
sm->timer_tick_enabled = 1;
eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm);
eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);
}
SM_STATE(SUPP_PAE, LOGOFF)
{
SM_ENTRY(SUPP_PAE, LOGOFF);
eapol_sm_txLogoff(sm);
sm->logoffSent = TRUE;
eapol_sm_set_port_unauthorized(sm);
}
SM_STATE(SUPP_PAE, DISCONNECTED)
{
SM_ENTRY(SUPP_PAE, DISCONNECTED);
sm->sPortMode = Auto;
sm->startCount = 0;
sm->eapTriggerStart = FALSE;
sm->logoffSent = FALSE;
eapol_sm_set_port_unauthorized(sm);
sm->suppAbort = TRUE;
sm->unicast_key_received = FALSE;
sm->broadcast_key_received = FALSE;
/*
* IEEE Std 802.1X-2004 does not clear heldWhile here, but doing so
* allows the timer tick to be stopped more quickly when the port is
* not enabled. Since this variable is used only within HELD state,
* clearing it on initialization does not change actual state machine
* behavior.
*/
sm->heldWhile = 0;
}
SM_STATE(SUPP_PAE, CONNECTING)
{
int send_start = sm->SUPP_PAE_state == SUPP_PAE_CONNECTING ||
sm->SUPP_PAE_state == SUPP_PAE_HELD;
SM_ENTRY(SUPP_PAE, CONNECTING);
if (sm->eapTriggerStart)
send_start = 1;
sm->eapTriggerStart = FALSE;
if (send_start) {
sm->startWhen = sm->startPeriod;
sm->startCount++;
} else {
/*
* Do not send EAPOL-Start immediately since in most cases,
* Authenticator is going to start authentication immediately
* after association and an extra EAPOL-Start is just going to
* delay authentication. Use a short timeout to send the first
* EAPOL-Start if Authenticator does not start authentication.
*/
if (sm->conf.wps && !(sm->conf.wps & EAPOL_PEER_IS_WPS20_AP)) {
/* Reduce latency on starting WPS negotiation. */
wpa_printf(MSG_DEBUG,
"EAPOL: Using shorter startWhen for WPS");
sm->startWhen = 1;
} else {
sm->startWhen = 2;
}
}
eapol_enable_timer_tick(sm);
sm->eapolEap = FALSE;
if (send_start)
eapol_sm_txStart(sm);
}
SM_STATE(SUPP_PAE, AUTHENTICATING)
{
SM_ENTRY(SUPP_PAE, AUTHENTICATING);
sm->startCount = 0;
sm->suppSuccess = FALSE;
sm->suppFail = FALSE;
sm->suppTimeout = FALSE;
sm->keyRun = FALSE;
sm->keyDone = FALSE;
sm->suppStart = TRUE;
}
SM_STATE(SUPP_PAE, HELD)
{
SM_ENTRY(SUPP_PAE, HELD);
sm->heldWhile = sm->heldPeriod;
eapol_enable_timer_tick(sm);
eapol_sm_set_port_unauthorized(sm);
sm->cb_status = EAPOL_CB_FAILURE;
}
SM_STATE(SUPP_PAE, AUTHENTICATED)
{
SM_ENTRY(SUPP_PAE, AUTHENTICATED);
eapol_sm_set_port_authorized(sm);
sm->cb_status = EAPOL_CB_SUCCESS;
}
SM_STATE(SUPP_PAE, RESTART)
{
SM_ENTRY(SUPP_PAE, RESTART);
sm->eapRestart = TRUE;
}
SM_STATE(SUPP_PAE, S_FORCE_AUTH)
{
SM_ENTRY(SUPP_PAE, S_FORCE_AUTH);
eapol_sm_set_port_authorized(sm);
sm->sPortMode = ForceAuthorized;
}
SM_STATE(SUPP_PAE, S_FORCE_UNAUTH)
{
SM_ENTRY(SUPP_PAE, S_FORCE_UNAUTH);
eapol_sm_set_port_unauthorized(sm);
sm->sPortMode = ForceUnauthorized;
eapol_sm_txLogoff(sm);
}
SM_STEP(SUPP_PAE)
{
if ((sm->userLogoff && !sm->logoffSent) &&
!(sm->initialize || !sm->portEnabled))
SM_ENTER_GLOBAL(SUPP_PAE, LOGOFF);
else if (((sm->portControl == Auto) &&
(sm->sPortMode != sm->portControl)) ||
sm->initialize || !sm->portEnabled)
SM_ENTER_GLOBAL(SUPP_PAE, DISCONNECTED);
else if ((sm->portControl == ForceAuthorized) &&
(sm->sPortMode != sm->portControl) &&
!(sm->initialize || !sm->portEnabled))
SM_ENTER_GLOBAL(SUPP_PAE, S_FORCE_AUTH);
else if ((sm->portControl == ForceUnauthorized) &&
(sm->sPortMode != sm->portControl) &&
!(sm->initialize || !sm->portEnabled))
SM_ENTER_GLOBAL(SUPP_PAE, S_FORCE_UNAUTH);
else switch (sm->SUPP_PAE_state) {
case SUPP_PAE_UNKNOWN:
break;
case SUPP_PAE_LOGOFF:
if (!sm->userLogoff)
SM_ENTER(SUPP_PAE, DISCONNECTED);
break;
case SUPP_PAE_DISCONNECTED:
SM_ENTER(SUPP_PAE, CONNECTING);
break;
case SUPP_PAE_CONNECTING:
if (sm->startWhen == 0 && sm->startCount < sm->maxStart)
SM_ENTER(SUPP_PAE, CONNECTING);
else if (sm->startWhen == 0 &&
sm->startCount >= sm->maxStart &&
sm->portValid)
SM_ENTER(SUPP_PAE, AUTHENTICATED);
else if (sm->eapSuccess || sm->eapFail)
SM_ENTER(SUPP_PAE, AUTHENTICATING);
else if (sm->eapolEap)
SM_ENTER(SUPP_PAE, RESTART);
else if (sm->startWhen == 0 &&
sm->startCount >= sm->maxStart &&
!sm->portValid)
SM_ENTER(SUPP_PAE, HELD);
break;
case SUPP_PAE_AUTHENTICATING:
if (sm->eapSuccess && !sm->portValid &&
sm->conf.accept_802_1x_keys &&
sm->conf.required_keys == 0) {
wpa_printf(MSG_DEBUG, "EAPOL: IEEE 802.1X for "
"plaintext connection; no EAPOL-Key frames "
"required");
sm->portValid = TRUE;
if (sm->ctx->eapol_done_cb)
sm->ctx->eapol_done_cb(sm->ctx->ctx);
}
if (sm->eapSuccess && sm->portValid)
SM_ENTER(SUPP_PAE, AUTHENTICATED);
else if (sm->eapFail || (sm->keyDone && !sm->portValid))
SM_ENTER(SUPP_PAE, HELD);
else if (sm->suppTimeout)
SM_ENTER(SUPP_PAE, CONNECTING);
else if (sm->eapTriggerStart)
SM_ENTER(SUPP_PAE, CONNECTING);
break;
case SUPP_PAE_HELD:
if (sm->heldWhile == 0)
SM_ENTER(SUPP_PAE, CONNECTING);
else if (sm->eapolEap)
SM_ENTER(SUPP_PAE, RESTART);
break;
case SUPP_PAE_AUTHENTICATED:
if (sm->eapolEap && sm->portValid)
SM_ENTER(SUPP_PAE, RESTART);
else if (!sm->portValid)
SM_ENTER(SUPP_PAE, DISCONNECTED);
break;
case SUPP_PAE_RESTART:
if (!sm->eapRestart)
SM_ENTER(SUPP_PAE, AUTHENTICATING);
break;
case SUPP_PAE_S_FORCE_AUTH:
break;
case SUPP_PAE_S_FORCE_UNAUTH:
break;
}
}
SM_STATE(KEY_RX, NO_KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, NO_KEY_RECEIVE);
}
SM_STATE(KEY_RX, KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, KEY_RECEIVE);
eapol_sm_processKey(sm);
sm->rxKey = FALSE;
}
SM_STEP(KEY_RX)
{
if (sm->initialize || !sm->portEnabled)
SM_ENTER_GLOBAL(KEY_RX, NO_KEY_RECEIVE);
switch (sm->KEY_RX_state) {
case KEY_RX_UNKNOWN:
break;
case KEY_RX_NO_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
case KEY_RX_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
}
}
SM_STATE(SUPP_BE, REQUEST)
{
SM_ENTRY(SUPP_BE, REQUEST);
sm->authWhile = 0;
sm->eapReq = TRUE;
eapol_sm_getSuppRsp(sm);
}
SM_STATE(SUPP_BE, RESPONSE)
{
SM_ENTRY(SUPP_BE, RESPONSE);
eapol_sm_txSuppRsp(sm);
sm->eapResp = FALSE;
}
SM_STATE(SUPP_BE, SUCCESS)
{
SM_ENTRY(SUPP_BE, SUCCESS);
sm->keyRun = TRUE;
sm->suppSuccess = TRUE;
#ifdef CONFIG_EAP_PROXY
if (sm->use_eap_proxy) {
if (eap_proxy_key_available(sm->eap_proxy)) {
/* New key received - clear IEEE 802.1X EAPOL-Key replay
* counter */
sm->replay_counter_valid = FALSE;
}
return;
}
#endif /* CONFIG_EAP_PROXY */
if (eap_key_available(sm->eap)) {
/* New key received - clear IEEE 802.1X EAPOL-Key replay
* counter */
sm->replay_counter_valid = FALSE;
}
}
SM_STATE(SUPP_BE, FAIL)
{
SM_ENTRY(SUPP_BE, FAIL);
sm->suppFail = TRUE;
}
SM_STATE(SUPP_BE, TIMEOUT)
{
SM_ENTRY(SUPP_BE, TIMEOUT);
sm->suppTimeout = TRUE;
}
SM_STATE(SUPP_BE, IDLE)
{
SM_ENTRY(SUPP_BE, IDLE);
sm->suppStart = FALSE;
sm->initial_req = TRUE;
}
SM_STATE(SUPP_BE, INITIALIZE)
{
SM_ENTRY(SUPP_BE, INITIALIZE);
eapol_sm_abortSupp(sm);
sm->suppAbort = FALSE;
/*
* IEEE Std 802.1X-2004 does not clear authWhile here, but doing so
* allows the timer tick to be stopped more quickly when the port is
* not enabled. Since this variable is used only within RECEIVE state,
* clearing it on initialization does not change actual state machine
* behavior.
*/
sm->authWhile = 0;
}
SM_STATE(SUPP_BE, RECEIVE)
{
SM_ENTRY(SUPP_BE, RECEIVE);
sm->authWhile = sm->authPeriod;
eapol_enable_timer_tick(sm);
sm->eapolEap = FALSE;
sm->eapNoResp = FALSE;
sm->initial_req = FALSE;
}
SM_STEP(SUPP_BE)
{
if (sm->initialize || sm->suppAbort)
SM_ENTER_GLOBAL(SUPP_BE, INITIALIZE);
else switch (sm->SUPP_BE_state) {
case SUPP_BE_UNKNOWN:
break;
case SUPP_BE_REQUEST:
/*
* IEEE Std 802.1X-2004 has transitions from REQUEST to FAIL
* and SUCCESS based on eapFail and eapSuccess, respectively.
* However, IEEE Std 802.1X-2004 is also specifying that
* eapNoResp should be set in conjunction with eapSuccess and
* eapFail which would mean that more than one of the
* transitions here would be activated at the same time.
* Skipping RESPONSE and/or RECEIVE states in these cases can
* cause problems and the direct transitions to do not seem
* correct. Because of this, the conditions for these
* transitions are verified only after eapNoResp. They are
* unlikely to be used since eapNoResp should always be set if
* either of eapSuccess or eapFail is set.
*/
if (sm->eapResp && sm->eapNoResp) {
wpa_printf(MSG_DEBUG, "EAPOL: SUPP_BE REQUEST: both "
"eapResp and eapNoResp set?!");
}
if (sm->eapResp)
SM_ENTER(SUPP_BE, RESPONSE);
else if (sm->eapNoResp)
SM_ENTER(SUPP_BE, RECEIVE);
else if (sm->eapFail)
SM_ENTER(SUPP_BE, FAIL);
else if (sm->eapSuccess)
SM_ENTER(SUPP_BE, SUCCESS);
break;
case SUPP_BE_RESPONSE:
SM_ENTER(SUPP_BE, RECEIVE);
break;
case SUPP_BE_SUCCESS:
SM_ENTER(SUPP_BE, IDLE);
break;
case SUPP_BE_FAIL:
SM_ENTER(SUPP_BE, IDLE);
break;
case SUPP_BE_TIMEOUT:
SM_ENTER(SUPP_BE, IDLE);
break;
case SUPP_BE_IDLE:
if (sm->eapFail && sm->suppStart)
SM_ENTER(SUPP_BE, FAIL);
else if (sm->eapolEap && sm->suppStart)
SM_ENTER(SUPP_BE, REQUEST);
else if (sm->eapSuccess && sm->suppStart)
SM_ENTER(SUPP_BE, SUCCESS);
break;
case SUPP_BE_INITIALIZE:
SM_ENTER(SUPP_BE, IDLE);
break;
case SUPP_BE_RECEIVE:
if (sm->eapolEap)
SM_ENTER(SUPP_BE, REQUEST);
else if (sm->eapFail)
SM_ENTER(SUPP_BE, FAIL);
else if (sm->authWhile == 0)
SM_ENTER(SUPP_BE, TIMEOUT);
else if (sm->eapSuccess)
SM_ENTER(SUPP_BE, SUCCESS);
break;
}
}
static void eapol_sm_txLogoff(struct eapol_sm *sm)
{
wpa_printf(MSG_DEBUG, "EAPOL: txLogoff");
sm->ctx->eapol_send(sm->ctx->eapol_send_ctx,
IEEE802_1X_TYPE_EAPOL_LOGOFF, (u8 *) "", 0);
sm->dot1xSuppEapolLogoffFramesTx++;
sm->dot1xSuppEapolFramesTx++;
}
static void eapol_sm_txStart(struct eapol_sm *sm)
{
wpa_printf(MSG_DEBUG, "EAPOL: txStart");
sm->ctx->eapol_send(sm->ctx->eapol_send_ctx,
IEEE802_1X_TYPE_EAPOL_START, (u8 *) "", 0);
sm->dot1xSuppEapolStartFramesTx++;
sm->dot1xSuppEapolFramesTx++;
}
#define IEEE8021X_ENCR_KEY_LEN 32
#define IEEE8021X_SIGN_KEY_LEN 32
struct eap_key_data {
u8 encr_key[IEEE8021X_ENCR_KEY_LEN];
u8 sign_key[IEEE8021X_SIGN_KEY_LEN];
};
static void eapol_sm_processKey(struct eapol_sm *sm)
{
#ifndef CONFIG_FIPS
struct ieee802_1x_hdr *hdr;
struct ieee802_1x_eapol_key *key;
struct eap_key_data keydata;
u8 orig_key_sign[IEEE8021X_KEY_SIGN_LEN], datakey[32];
#ifndef CONFIG_NO_RC4
u8 ekey[IEEE8021X_KEY_IV_LEN + IEEE8021X_ENCR_KEY_LEN];
#endif /* CONFIG_NO_RC4 */
int key_len, res, sign_key_len, encr_key_len;
u16 rx_key_length;
size_t plen;
wpa_printf(MSG_DEBUG, "EAPOL: processKey");
if (sm->last_rx_key == NULL)
return;
if (!sm->conf.accept_802_1x_keys) {
wpa_printf(MSG_WARNING, "EAPOL: Received IEEE 802.1X EAPOL-Key"
" even though this was not accepted - "
"ignoring this packet");
return;
}
if (sm->last_rx_key_len < sizeof(*hdr) + sizeof(*key))
return;
hdr = (struct ieee802_1x_hdr *) sm->last_rx_key;
key = (struct ieee802_1x_eapol_key *) (hdr + 1);
plen = be_to_host16(hdr->length);
if (sizeof(*hdr) + plen > sm->last_rx_key_len || plen < sizeof(*key)) {
wpa_printf(MSG_WARNING, "EAPOL: Too short EAPOL-Key frame");
return;
}
rx_key_length = WPA_GET_BE16(key->key_length);
wpa_printf(MSG_DEBUG, "EAPOL: RX IEEE 802.1X ver=%d type=%d len=%d "
"EAPOL-Key: type=%d key_length=%d key_index=0x%x",
hdr->version, hdr->type, be_to_host16(hdr->length),
key->type, rx_key_length, key->key_index);
eapol_sm_notify_lower_layer_success(sm, 1);
sign_key_len = IEEE8021X_SIGN_KEY_LEN;
encr_key_len = IEEE8021X_ENCR_KEY_LEN;
res = eapol_sm_get_key(sm, (u8 *) &keydata, sizeof(keydata));
if (res < 0) {
wpa_printf(MSG_DEBUG, "EAPOL: Could not get master key for "
"decrypting EAPOL-Key keys");
return;
}
if (res == 16) {
/* LEAP derives only 16 bytes of keying material. */
res = eapol_sm_get_key(sm, (u8 *) &keydata, 16);
if (res) {
wpa_printf(MSG_DEBUG, "EAPOL: Could not get LEAP "
"master key for decrypting EAPOL-Key keys");
return;
}
sign_key_len = 16;
encr_key_len = 16;
os_memcpy(keydata.sign_key, keydata.encr_key, 16);
} else if (res) {
wpa_printf(MSG_DEBUG, "EAPOL: Could not get enough master key "
"data for decrypting EAPOL-Key keys (res=%d)", res);
return;
}
/* The key replay_counter must increase when same master key */
if (sm->replay_counter_valid &&
os_memcmp(sm->last_replay_counter, key->replay_counter,
IEEE8021X_REPLAY_COUNTER_LEN) >= 0) {
wpa_printf(MSG_WARNING, "EAPOL: EAPOL-Key replay counter did "
"not increase - ignoring key");
wpa_hexdump(MSG_DEBUG, "EAPOL: last replay counter",
sm->last_replay_counter,
IEEE8021X_REPLAY_COUNTER_LEN);
wpa_hexdump(MSG_DEBUG, "EAPOL: received replay counter",
key->replay_counter, IEEE8021X_REPLAY_COUNTER_LEN);
return;
}
/* Verify key signature (HMAC-MD5) */
os_memcpy(orig_key_sign, key->key_signature, IEEE8021X_KEY_SIGN_LEN);
os_memset(key->key_signature, 0, IEEE8021X_KEY_SIGN_LEN);
hmac_md5(keydata.sign_key, sign_key_len,
sm->last_rx_key, sizeof(*hdr) + be_to_host16(hdr->length),
key->key_signature);
if (os_memcmp_const(orig_key_sign, key->key_signature,
IEEE8021X_KEY_SIGN_LEN) != 0) {
wpa_printf(MSG_DEBUG, "EAPOL: Invalid key signature in "
"EAPOL-Key packet");
os_memcpy(key->key_signature, orig_key_sign,
IEEE8021X_KEY_SIGN_LEN);
return;
}
wpa_printf(MSG_DEBUG, "EAPOL: EAPOL-Key key signature verified");
key_len = plen - sizeof(*key);
if (key_len > 32 || rx_key_length > 32) {
wpa_printf(MSG_WARNING, "EAPOL: Too long key data length %d",
key_len ? key_len : rx_key_length);
return;
}
if (key_len == rx_key_length) {
#ifdef CONFIG_NO_RC4
if (encr_key_len) {
/* otherwise unused */
}
wpa_printf(MSG_ERROR, "EAPOL: RC4 not supported in the build");
return;
#else /* CONFIG_NO_RC4 */
os_memcpy(ekey, key->key_iv, IEEE8021X_KEY_IV_LEN);
os_memcpy(ekey + IEEE8021X_KEY_IV_LEN, keydata.encr_key,
encr_key_len);
os_memcpy(datakey, key + 1, key_len);
rc4_skip(ekey, IEEE8021X_KEY_IV_LEN + encr_key_len, 0,
datakey, key_len);
wpa_hexdump_key(MSG_DEBUG, "EAPOL: Decrypted(RC4) key",
datakey, key_len);
#endif /* CONFIG_NO_RC4 */
} else if (key_len == 0) {
/*
* IEEE 802.1X-2004 specifies that least significant Key Length
* octets from MS-MPPE-Send-Key are used as the key if the key
* data is not present. This seems to be meaning the beginning
* of the MS-MPPE-Send-Key. In addition, MS-MPPE-Send-Key in
* Supplicant corresponds to MS-MPPE-Recv-Key in Authenticator.
* Anyway, taking the beginning of the keying material from EAP
* seems to interoperate with Authenticators.
*/
key_len = rx_key_length;
os_memcpy(datakey, keydata.encr_key, key_len);
wpa_hexdump_key(MSG_DEBUG, "EAPOL: using part of EAP keying "
"material data encryption key",
datakey, key_len);
} else {
wpa_printf(MSG_DEBUG, "EAPOL: Invalid key data length %d "
"(key_length=%d)", key_len, rx_key_length);
return;
}
sm->replay_counter_valid = TRUE;
os_memcpy(sm->last_replay_counter, key->replay_counter,
IEEE8021X_REPLAY_COUNTER_LEN);
wpa_printf(MSG_DEBUG, "EAPOL: Setting dynamic WEP key: %s keyidx %d "
"len %d",
key->key_index & IEEE8021X_KEY_INDEX_FLAG ?
"unicast" : "broadcast",
key->key_index & IEEE8021X_KEY_INDEX_MASK, key_len);
if (sm->ctx->set_wep_key &&
sm->ctx->set_wep_key(sm->ctx->ctx,
key->key_index & IEEE8021X_KEY_INDEX_FLAG,
key->key_index & IEEE8021X_KEY_INDEX_MASK,
datakey, key_len) < 0) {
wpa_printf(MSG_WARNING, "EAPOL: Failed to set WEP key to the "
" driver.");
} else {
if (key->key_index & IEEE8021X_KEY_INDEX_FLAG)
sm->unicast_key_received = TRUE;
else
sm->broadcast_key_received = TRUE;
if ((sm->unicast_key_received ||
!(sm->conf.required_keys & EAPOL_REQUIRE_KEY_UNICAST)) &&
(sm->broadcast_key_received ||
!(sm->conf.required_keys & EAPOL_REQUIRE_KEY_BROADCAST)))
{
wpa_printf(MSG_DEBUG, "EAPOL: all required EAPOL-Key "
"frames received");
sm->portValid = TRUE;
if (sm->ctx->eapol_done_cb)
sm->ctx->eapol_done_cb(sm->ctx->ctx);
}
}
#endif /* CONFIG_FIPS */
}
static void eapol_sm_getSuppRsp(struct eapol_sm *sm)
{
wpa_printf(MSG_DEBUG, "EAPOL: getSuppRsp");
/* EAP layer processing; no special code is needed, since Supplicant
* Backend state machine is waiting for eapNoResp or eapResp to be set
* and these are only set in the EAP state machine when the processing
* has finished. */
}
static void eapol_sm_txSuppRsp(struct eapol_sm *sm)
{
struct wpabuf *resp;
wpa_printf(MSG_DEBUG, "EAPOL: txSuppRsp");
#ifdef CONFIG_EAP_PROXY
if (sm->use_eap_proxy) {
/* Get EAP Response from EAP Proxy */
resp = eap_proxy_get_eapRespData(sm->eap_proxy);
if (resp == NULL) {
wpa_printf(MSG_WARNING, "EAPOL: txSuppRsp - EAP Proxy "
"response data not available");
return;
}
} else
#endif /* CONFIG_EAP_PROXY */
resp = eap_get_eapRespData(sm->eap);
if (resp == NULL) {
wpa_printf(MSG_WARNING, "EAPOL: txSuppRsp - EAP response data "
"not available");
return;
}
/* Send EAP-Packet from the EAP layer to the Authenticator */
sm->ctx->eapol_send(sm->ctx->eapol_send_ctx,
IEEE802_1X_TYPE_EAP_PACKET, wpabuf_head(resp),
wpabuf_len(resp));
/* eapRespData is not used anymore, so free it here */
wpabuf_free(resp);
if (sm->initial_req)
sm->dot1xSuppEapolReqIdFramesRx++;
else
sm->dot1xSuppEapolReqFramesRx++;
sm->dot1xSuppEapolRespFramesTx++;
sm->dot1xSuppEapolFramesTx++;
}
static void eapol_sm_abortSupp(struct eapol_sm *sm)
{
/* release system resources that may have been allocated for the
* authentication session */
os_free(sm->last_rx_key);
sm->last_rx_key = NULL;
wpabuf_free(sm->eapReqData);
sm->eapReqData = NULL;
eap_sm_abort(sm->eap);
}
static void eapol_sm_step_timeout(void *eloop_ctx, void *timeout_ctx)
{
eapol_sm_step(timeout_ctx);
}
static void eapol_sm_set_port_authorized(struct eapol_sm *sm)
{
int cb;
cb = sm->suppPortStatus != Authorized || sm->force_authorized_update;
sm->force_authorized_update = FALSE;
sm->suppPortStatus = Authorized;
if (cb && sm->ctx->port_cb)
sm->ctx->port_cb(sm->ctx->ctx, 1);
}
static void eapol_sm_set_port_unauthorized(struct eapol_sm *sm)
{
int cb;
cb = sm->suppPortStatus != Unauthorized || sm->force_authorized_update;
sm->force_authorized_update = FALSE;
sm->suppPortStatus = Unauthorized;
if (cb && sm->ctx->port_cb)
sm->ctx->port_cb(sm->ctx->ctx, 0);
}
/**
* eapol_sm_step - EAPOL state machine step function
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* This function is called to notify the state machine about changed external
* variables. It will step through the EAPOL state machines in loop to process
* all triggered state changes.
*/
void eapol_sm_step(struct eapol_sm *sm)
{
int i;
/* In theory, it should be ok to run this in loop until !changed.
* However, it is better to use a limit on number of iterations to
* allow events (e.g., SIGTERM) to stop the program cleanly if the
* state machine were to generate a busy loop. */
for (i = 0; i < 100; i++) {
sm->changed = FALSE;
SM_STEP_RUN(SUPP_PAE);
SM_STEP_RUN(KEY_RX);
SM_STEP_RUN(SUPP_BE);
#ifdef CONFIG_EAP_PROXY
if (sm->use_eap_proxy) {
/* Drive the EAP proxy state machine */
if (eap_proxy_sm_step(sm->eap_proxy, sm->eap))
sm->changed = TRUE;
} else
#endif /* CONFIG_EAP_PROXY */
if (eap_peer_sm_step(sm->eap))
sm->changed = TRUE;
if (!sm->changed)
break;
}
if (sm->changed) {
/* restart EAPOL state machine step from timeout call in order
* to allow other events to be processed. */
eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm);
eloop_register_timeout(0, 0, eapol_sm_step_timeout, NULL, sm);
}
if (sm->ctx->cb && sm->cb_status != EAPOL_CB_IN_PROGRESS) {
enum eapol_supp_result result;
if (sm->cb_status == EAPOL_CB_SUCCESS)
result = EAPOL_SUPP_RESULT_SUCCESS;
else if (eap_peer_was_failure_expected(sm->eap))
result = EAPOL_SUPP_RESULT_EXPECTED_FAILURE;
else
result = EAPOL_SUPP_RESULT_FAILURE;
sm->cb_status = EAPOL_CB_IN_PROGRESS;
sm->ctx->cb(sm, result, sm->ctx->cb_ctx);
}
}
#ifdef CONFIG_CTRL_IFACE
static const char *eapol_supp_pae_state(int state)
{
switch (state) {
case SUPP_PAE_LOGOFF:
return "LOGOFF";
case SUPP_PAE_DISCONNECTED:
return "DISCONNECTED";
case SUPP_PAE_CONNECTING:
return "CONNECTING";
case SUPP_PAE_AUTHENTICATING:
return "AUTHENTICATING";
case SUPP_PAE_HELD:
return "HELD";
case SUPP_PAE_AUTHENTICATED:
return "AUTHENTICATED";
case SUPP_PAE_RESTART:
return "RESTART";
default:
return "UNKNOWN";
}
}
static const char *eapol_supp_be_state(int state)
{
switch (state) {
case SUPP_BE_REQUEST:
return "REQUEST";
case SUPP_BE_RESPONSE:
return "RESPONSE";
case SUPP_BE_SUCCESS:
return "SUCCESS";
case SUPP_BE_FAIL:
return "FAIL";
case SUPP_BE_TIMEOUT:
return "TIMEOUT";
case SUPP_BE_IDLE:
return "IDLE";
case SUPP_BE_INITIALIZE:
return "INITIALIZE";
case SUPP_BE_RECEIVE:
return "RECEIVE";
default:
return "UNKNOWN";
}
}
static const char * eapol_port_status(PortStatus status)
{
if (status == Authorized)
return "Authorized";
else
return "Unauthorized";
}
#endif /* CONFIG_CTRL_IFACE */
#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG)
static const char * eapol_port_control(PortControl ctrl)
{
switch (ctrl) {
case Auto:
return "Auto";
case ForceUnauthorized:
return "ForceUnauthorized";
case ForceAuthorized:
return "ForceAuthorized";
default:
return "Unknown";
}
}
#endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */
/**
* eapol_sm_configure - Set EAPOL variables
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @heldPeriod: dot1xSuppHeldPeriod
* @authPeriod: dot1xSuppAuthPeriod
* @startPeriod: dot1xSuppStartPeriod
* @maxStart: dot1xSuppMaxStart
*
* Set configurable EAPOL state machine variables. Each variable can be set to
* the given value or ignored if set to -1 (to set only some of the variables).
*/
void eapol_sm_configure(struct eapol_sm *sm, int heldPeriod, int authPeriod,
int startPeriod, int maxStart)
{
if (sm == NULL)
return;
if (heldPeriod >= 0)
sm->heldPeriod = heldPeriod;
if (authPeriod >= 0)
sm->authPeriod = authPeriod;
if (startPeriod >= 0)
sm->startPeriod = startPeriod;
if (maxStart >= 0)
sm->maxStart = maxStart;
}
/**
* eapol_sm_get_method_name - Get EAPOL method name
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* Returns: Static string containing name of current eap method or NULL
*/
const char * eapol_sm_get_method_name(struct eapol_sm *sm)
{
if (sm->SUPP_PAE_state != SUPP_PAE_AUTHENTICATED ||
sm->suppPortStatus != Authorized)
return NULL;
return eap_sm_get_method_name(sm->eap);
}
#ifdef CONFIG_CTRL_IFACE
/**
* eapol_sm_get_status - Get EAPOL state machine status
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @buf: Buffer for status information
* @buflen: Maximum buffer length
* @verbose: Whether to include verbose status information
* Returns: Number of bytes written to buf.
*
* Query EAPOL state machine for status information. This function fills in a
* text area with current status information from the EAPOL state machine. If
* the buffer (buf) is not large enough, status information will be truncated
* to fit the buffer.
*/
int eapol_sm_get_status(struct eapol_sm *sm, char *buf, size_t buflen,
int verbose)
{
int len, ret;
if (sm == NULL)
return 0;
len = os_snprintf(buf, buflen,
"Supplicant PAE state=%s\n"
"suppPortStatus=%s\n",
eapol_supp_pae_state(sm->SUPP_PAE_state),
eapol_port_status(sm->suppPortStatus));
if (os_snprintf_error(buflen, len))
return 0;
if (verbose) {
ret = os_snprintf(buf + len, buflen - len,
"heldPeriod=%u\n"
"authPeriod=%u\n"
"startPeriod=%u\n"
"maxStart=%u\n"
"portControl=%s\n"
"Supplicant Backend state=%s\n",
sm->heldPeriod,
sm->authPeriod,
sm->startPeriod,
sm->maxStart,
eapol_port_control(sm->portControl),
eapol_supp_be_state(sm->SUPP_BE_state));
if (os_snprintf_error(buflen - len, ret))
return len;
len += ret;
}
#ifdef CONFIG_EAP_PROXY
if (sm->use_eap_proxy)
len += eap_proxy_sm_get_status(sm->eap_proxy,
buf + len, buflen - len,
verbose);
else
#endif /* CONFIG_EAP_PROXY */
len += eap_sm_get_status(sm->eap, buf + len, buflen - len, verbose);
return len;
}
/**
* eapol_sm_get_mib - Get EAPOL state machine MIBs
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @buf: Buffer for MIB information
* @buflen: Maximum buffer length
* Returns: Number of bytes written to buf.
*
* Query EAPOL state machine for MIB information. This function fills in a
* text area with current MIB information from the EAPOL state machine. If
* the buffer (buf) is not large enough, MIB information will be truncated to
* fit the buffer.
*/
int eapol_sm_get_mib(struct eapol_sm *sm, char *buf, size_t buflen)
{
size_t len;
int ret;
if (sm == NULL)
return 0;
ret = os_snprintf(buf, buflen,
"dot1xSuppPaeState=%d\n"
"dot1xSuppHeldPeriod=%u\n"
"dot1xSuppAuthPeriod=%u\n"
"dot1xSuppStartPeriod=%u\n"
"dot1xSuppMaxStart=%u\n"
"dot1xSuppSuppControlledPortStatus=%s\n"
"dot1xSuppBackendPaeState=%d\n",
sm->SUPP_PAE_state,
sm->heldPeriod,
sm->authPeriod,
sm->startPeriod,
sm->maxStart,
sm->suppPortStatus == Authorized ?
"Authorized" : "Unauthorized",
sm->SUPP_BE_state);
if (os_snprintf_error(buflen, ret))
return 0;
len = ret;
ret = os_snprintf(buf + len, buflen - len,
"dot1xSuppEapolFramesRx=%u\n"
"dot1xSuppEapolFramesTx=%u\n"
"dot1xSuppEapolStartFramesTx=%u\n"
"dot1xSuppEapolLogoffFramesTx=%u\n"
"dot1xSuppEapolRespFramesTx=%u\n"
"dot1xSuppEapolReqIdFramesRx=%u\n"
"dot1xSuppEapolReqFramesRx=%u\n"
"dot1xSuppInvalidEapolFramesRx=%u\n"
"dot1xSuppEapLengthErrorFramesRx=%u\n"
"dot1xSuppLastEapolFrameVersion=%u\n"
"dot1xSuppLastEapolFrameSource=" MACSTR "\n",
sm->dot1xSuppEapolFramesRx,
sm->dot1xSuppEapolFramesTx,
sm->dot1xSuppEapolStartFramesTx,
sm->dot1xSuppEapolLogoffFramesTx,
sm->dot1xSuppEapolRespFramesTx,
sm->dot1xSuppEapolReqIdFramesRx,
sm->dot1xSuppEapolReqFramesRx,
sm->dot1xSuppInvalidEapolFramesRx,
sm->dot1xSuppEapLengthErrorFramesRx,
sm->dot1xSuppLastEapolFrameVersion,
MAC2STR(sm->dot1xSuppLastEapolFrameSource));
if (os_snprintf_error(buflen - len, ret))
return len;
len += ret;
return len;
}
#endif /* CONFIG_CTRL_IFACE */
/**
* eapol_sm_rx_eapol - Process received EAPOL frames
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @src: Source MAC address of the EAPOL packet
* @buf: Pointer to the beginning of the EAPOL data (EAPOL header)
* @len: Length of the EAPOL frame
* Returns: 1 = EAPOL frame processed, 0 = not for EAPOL state machine,
* -1 failure
*/
int eapol_sm_rx_eapol(struct eapol_sm *sm, const u8 *src, const u8 *buf,
size_t len)
{
const struct ieee802_1x_hdr *hdr;
const struct ieee802_1x_eapol_key *key;
int data_len;
int res = 1;
size_t plen;
if (sm == NULL)
return 0;
sm->dot1xSuppEapolFramesRx++;
if (len < sizeof(*hdr)) {
sm->dot1xSuppInvalidEapolFramesRx++;
return 0;
}
hdr = (const struct ieee802_1x_hdr *) buf;
sm->dot1xSuppLastEapolFrameVersion = hdr->version;
os_memcpy(sm->dot1xSuppLastEapolFrameSource, src, ETH_ALEN);
if (hdr->version < EAPOL_VERSION) {
/* TODO: backwards compatibility */
}
plen = be_to_host16(hdr->length);
if (plen > len - sizeof(*hdr)) {
sm->dot1xSuppEapLengthErrorFramesRx++;
return 0;
}
#ifdef CONFIG_WPS
if (sm->conf.wps && sm->conf.workaround &&
plen < len - sizeof(*hdr) &&
hdr->type == IEEE802_1X_TYPE_EAP_PACKET &&
len - sizeof(*hdr) > sizeof(struct eap_hdr)) {
const struct eap_hdr *ehdr =
(const struct eap_hdr *) (hdr + 1);
u16 elen;
elen = be_to_host16(ehdr->length);
if (elen > plen && elen <= len - sizeof(*hdr)) {
/*
* Buffalo WHR-G125 Ver.1.47 seems to send EAP-WPS
* packets with too short EAPOL header length field
* (14 octets). This is fixed in firmware Ver.1.49.
* As a workaround, fix the EAPOL header based on the
* correct length in the EAP packet.
*/
wpa_printf(MSG_DEBUG, "EAPOL: Workaround - fix EAPOL "
"payload length based on EAP header: "
"%d -> %d", (int) plen, elen);
plen = elen;
}
}
#endif /* CONFIG_WPS */
data_len = plen + sizeof(*hdr);
switch (hdr->type) {
case IEEE802_1X_TYPE_EAP_PACKET:
if (sm->conf.workaround) {
/*
* An AP has been reported to send out EAP message with
* undocumented code 10 at some point near the
* completion of EAP authentication. This can result in
* issues with the unexpected EAP message triggering
* restart of EAPOL authentication. Avoid this by
* skipping the message without advancing the state
* machine.
*/
const struct eap_hdr *ehdr =
(const struct eap_hdr *) (hdr + 1);
if (plen >= sizeof(*ehdr) && ehdr->code == 10) {
wpa_printf(MSG_DEBUG, "EAPOL: Ignore EAP packet with unknown code 10");
break;
}
}
if (sm->cached_pmk) {
/* Trying to use PMKSA caching, but Authenticator did
* not seem to have a matching entry. Need to restart
* EAPOL state machines.
*/
eapol_sm_abort_cached(sm);
}
wpabuf_free(sm->eapReqData);
sm->eapReqData = wpabuf_alloc_copy(hdr + 1, plen);
if (sm->eapReqData) {
wpa_printf(MSG_DEBUG, "EAPOL: Received EAP-Packet "
"frame");
sm->eapolEap = TRUE;
#ifdef CONFIG_EAP_PROXY
if (sm->use_eap_proxy) {
eap_proxy_packet_update(
sm->eap_proxy,
wpabuf_mhead_u8(sm->eapReqData),
wpabuf_len(sm->eapReqData));
wpa_printf(MSG_DEBUG, "EAPOL: eap_proxy "
"EAP Req updated");
}
#endif /* CONFIG_EAP_PROXY */
eapol_sm_step(sm);
}
break;
case IEEE802_1X_TYPE_EAPOL_KEY:
if (plen < sizeof(*key)) {
wpa_printf(MSG_DEBUG, "EAPOL: Too short EAPOL-Key "
"frame received");
break;
}
key = (const struct ieee802_1x_eapol_key *) (hdr + 1);
if (key->type == EAPOL_KEY_TYPE_WPA ||
key->type == EAPOL_KEY_TYPE_RSN) {
/* WPA Supplicant takes care of this frame. */
wpa_printf(MSG_DEBUG, "EAPOL: Ignoring WPA EAPOL-Key "
"frame in EAPOL state machines");
res = 0;
break;
}
if (key->type != EAPOL_KEY_TYPE_RC4) {
wpa_printf(MSG_DEBUG, "EAPOL: Ignored unknown "
"EAPOL-Key type %d", key->type);
break;
}
os_free(sm->last_rx_key);
sm->last_rx_key = os_malloc(data_len);
if (sm->last_rx_key) {
wpa_printf(MSG_DEBUG, "EAPOL: Received EAPOL-Key "
"frame");
os_memcpy(sm->last_rx_key, buf, data_len);
sm->last_rx_key_len = data_len;
sm->rxKey = TRUE;
eapol_sm_step(sm);
}
break;
#ifdef CONFIG_MACSEC
case IEEE802_1X_TYPE_EAPOL_MKA:
wpa_printf(MSG_EXCESSIVE,
"EAPOL type %d will be handled by MKA",
hdr->type);
break;
#endif /* CONFIG_MACSEC */
default:
wpa_printf(MSG_DEBUG, "EAPOL: Received unknown EAPOL type %d",
hdr->type);
sm->dot1xSuppInvalidEapolFramesRx++;
break;
}
return res;
}
/**
* eapol_sm_notify_tx_eapol_key - Notification about transmitted EAPOL packet
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* Notify EAPOL state machine about transmitted EAPOL packet from an external
* component, e.g., WPA. This will update the statistics.
*/
void eapol_sm_notify_tx_eapol_key(struct eapol_sm *sm)
{
if (sm)
sm->dot1xSuppEapolFramesTx++;
}
/**
* eapol_sm_notify_portEnabled - Notification about portEnabled change
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @enabled: New portEnabled value
*
* Notify EAPOL state machine about new portEnabled value.
*/
void eapol_sm_notify_portEnabled(struct eapol_sm *sm, Boolean enabled)
{
if (sm == NULL)
return;
wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
"portEnabled=%d", enabled);
if (sm->portEnabled != enabled)
sm->force_authorized_update = TRUE;
sm->portEnabled = enabled;
eapol_sm_step(sm);
}
/**
* eapol_sm_notify_portValid - Notification about portValid change
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @valid: New portValid value
*
* Notify EAPOL state machine about new portValid value.
*/
void eapol_sm_notify_portValid(struct eapol_sm *sm, Boolean valid)
{
if (sm == NULL)
return;
wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
"portValid=%d", valid);
sm->portValid = valid;
eapol_sm_step(sm);
}
/**
* eapol_sm_notify_eap_success - Notification of external EAP success trigger
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @success: %TRUE = set success, %FALSE = clear success
*
* Notify the EAPOL state machine that external event has forced EAP state to
* success (success = %TRUE). This can be cleared by setting success = %FALSE.
*
* This function is called to update EAP state when WPA-PSK key handshake has
* been completed successfully since WPA-PSK does not use EAP state machine.
*/
void eapol_sm_notify_eap_success(struct eapol_sm *sm, Boolean success)
{
if (sm == NULL)
return;
wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
"EAP success=%d", success);
sm->eapSuccess = success;
sm->altAccept = success;
if (success)
eap_notify_success(sm->eap);
eapol_sm_step(sm);
}
/**
* eapol_sm_notify_eap_fail - Notification of external EAP failure trigger
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @fail: %TRUE = set failure, %FALSE = clear failure
*
* Notify EAPOL state machine that external event has forced EAP state to
* failure (fail = %TRUE). This can be cleared by setting fail = %FALSE.
*/
void eapol_sm_notify_eap_fail(struct eapol_sm *sm, Boolean fail)
{
if (sm == NULL)
return;
wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
"EAP fail=%d", fail);
sm->eapFail = fail;
sm->altReject = fail;
eapol_sm_step(sm);
}
/**
* eapol_sm_notify_config - Notification of EAPOL configuration change
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @config: Pointer to current network EAP configuration
* @conf: Pointer to EAPOL configuration data
*
* Notify EAPOL state machine that configuration has changed. config will be
* stored as a backpointer to network configuration. This can be %NULL to clear
* the stored pointed. conf will be copied to local EAPOL/EAP configuration
* data. If conf is %NULL, this part of the configuration change will be
* skipped.
*/
void eapol_sm_notify_config(struct eapol_sm *sm,
struct eap_peer_config *config,
const struct eapol_config *conf)
{
if (sm == NULL)
return;
sm->config = config;
#ifdef CONFIG_EAP_PROXY
sm->use_eap_proxy = eap_proxy_notify_config(sm->eap_proxy, config) > 0;
#endif /* CONFIG_EAP_PROXY */
if (conf == NULL)
return;
sm->conf.accept_802_1x_keys = conf->accept_802_1x_keys;
sm->conf.required_keys = conf->required_keys;
sm->conf.fast_reauth = conf->fast_reauth;
sm->conf.workaround = conf->workaround;
sm->conf.wps = conf->wps;
#ifdef CONFIG_EAP_PROXY
if (sm->use_eap_proxy) {
/* Using EAP Proxy, so skip EAP state machine update */
return;
}
#endif /* CONFIG_EAP_PROXY */
if (sm->eap) {
eap_set_fast_reauth(sm->eap, conf->fast_reauth);
eap_set_workaround(sm->eap, conf->workaround);
eap_set_force_disabled(sm->eap, conf->eap_disabled);
eap_set_external_sim(sm->eap, conf->external_sim);
}
}
/**
* eapol_sm_get_key - Get master session key (MSK) from EAP
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @key: Pointer for key buffer
* @len: Number of bytes to copy to key
* Returns: 0 on success (len of key available), maximum available key len
* (>0) if key is available but it is shorter than len, or -1 on failure.
*
* Fetch EAP keying material (MSK, eapKeyData) from EAP state machine. The key
* is available only after a successful authentication.
*/
int eapol_sm_get_key(struct eapol_sm *sm, u8 *key, size_t len)
{
const u8 *eap_key;
size_t eap_len;
#ifdef CONFIG_EAP_PROXY
if (sm && sm->use_eap_proxy) {
/* Get key from EAP proxy */
if (sm == NULL || !eap_proxy_key_available(sm->eap_proxy)) {
wpa_printf(MSG_DEBUG, "EAPOL: EAP key not available");
return -1;
}
eap_key = eap_proxy_get_eapKeyData(sm->eap_proxy, &eap_len);
if (eap_key == NULL) {
wpa_printf(MSG_DEBUG, "EAPOL: Failed to get "
"eapKeyData");
return -1;
}
goto key_fetched;
}
#endif /* CONFIG_EAP_PROXY */
if (sm == NULL || !eap_key_available(sm->eap)) {
wpa_printf(MSG_DEBUG, "EAPOL: EAP key not available");
return -1;
}
eap_key = eap_get_eapKeyData(sm->eap, &eap_len);
if (eap_key == NULL) {
wpa_printf(MSG_DEBUG, "EAPOL: Failed to get eapKeyData");
return -1;
}
#ifdef CONFIG_EAP_PROXY
key_fetched:
#endif /* CONFIG_EAP_PROXY */
if (len > eap_len) {
wpa_printf(MSG_DEBUG, "EAPOL: Requested key length (%lu) not "
"available (len=%lu)",
(unsigned long) len, (unsigned long) eap_len);
return eap_len;
}
os_memcpy(key, eap_key, len);
wpa_printf(MSG_DEBUG, "EAPOL: Successfully fetched key (len=%lu)",
(unsigned long) len);
return 0;
}
/**
* eapol_sm_get_session_id - Get EAP Session-Id
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @len: Pointer to variable that will be set to number of bytes in the session
* Returns: Pointer to the EAP Session-Id or %NULL on failure
*
* The Session-Id is available only after a successful authentication.
*/
const u8 * eapol_sm_get_session_id(struct eapol_sm *sm, size_t *len)
{
if (sm == NULL || !eap_key_available(sm->eap)) {
wpa_printf(MSG_DEBUG, "EAPOL: EAP Session-Id not available");
return NULL;
}
return eap_get_eapSessionId(sm->eap, len);
}
/**
* eapol_sm_notify_logoff - Notification of logon/logoff commands
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @logoff: Whether command was logoff
*
* Notify EAPOL state machines that user requested logon/logoff.
*/
void eapol_sm_notify_logoff(struct eapol_sm *sm, Boolean logoff)
{
if (sm) {
sm->userLogoff = logoff;
if (!logoff) {
/* If there is a delayed txStart queued, start now. */
sm->startWhen = 0;
}
eapol_sm_step(sm);
}
}
/**
* eapol_sm_notify_pmkid_attempt - Notification of successful PMKSA caching
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* Notify EAPOL state machines that PMKSA caching was successful. This is used
* to move EAPOL and EAP state machines into authenticated/successful state.
*/
void eapol_sm_notify_cached(struct eapol_sm *sm)
{
if (sm == NULL)
return;
wpa_printf(MSG_DEBUG, "EAPOL: PMKSA caching was used - skip EAPOL");
sm->eapSuccess = TRUE;
eap_notify_success(sm->eap);
eapol_sm_step(sm);
}
/**
* eapol_sm_notify_pmkid_attempt - Notification of PMKSA caching
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* Notify EAPOL state machines if PMKSA caching is used.
*/
void eapol_sm_notify_pmkid_attempt(struct eapol_sm *sm)
{
if (sm == NULL)
return;
wpa_printf(MSG_DEBUG, "RSN: Trying to use cached PMKSA");
sm->cached_pmk = TRUE;
}
static void eapol_sm_abort_cached(struct eapol_sm *sm)
{
wpa_printf(MSG_DEBUG, "RSN: Authenticator did not accept PMKID, "
"doing full EAP authentication");
if (sm == NULL)
return;
sm->cached_pmk = FALSE;
sm->SUPP_PAE_state = SUPP_PAE_CONNECTING;
eapol_sm_set_port_unauthorized(sm);
/* Make sure we do not start sending EAPOL-Start frames first, but
* instead move to RESTART state to start EAPOL authentication. */
sm->startWhen = 3;
eapol_enable_timer_tick(sm);
if (sm->ctx->aborted_cached)
sm->ctx->aborted_cached(sm->ctx->ctx);
}
/**
* eapol_sm_register_scard_ctx - Notification of smart card context
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @ctx: Context data for smart card operations
*
* Notify EAPOL state machines of context data for smart card operations. This
* context data will be used as a parameter for scard_*() functions.
*/
void eapol_sm_register_scard_ctx(struct eapol_sm *sm, void *ctx)
{
if (sm) {
sm->ctx->scard_ctx = ctx;
eap_register_scard_ctx(sm->eap, ctx);
}
}
/**
* eapol_sm_notify_portControl - Notification of portControl changes
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @portControl: New value for portControl variable
*
* Notify EAPOL state machines that portControl variable has changed.
*/
void eapol_sm_notify_portControl(struct eapol_sm *sm, PortControl portControl)
{
if (sm == NULL)
return;
wpa_printf(MSG_DEBUG, "EAPOL: External notification - "
"portControl=%s", eapol_port_control(portControl));
sm->portControl = portControl;
eapol_sm_step(sm);
}
/**
* eapol_sm_notify_ctrl_attached - Notification of attached monitor
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* Notify EAPOL state machines that a monitor was attached to the control
* interface to trigger re-sending of pending requests for user input.
*/
void eapol_sm_notify_ctrl_attached(struct eapol_sm *sm)
{
if (sm == NULL)
return;
eap_sm_notify_ctrl_attached(sm->eap);
}
/**
* eapol_sm_notify_ctrl_response - Notification of received user input
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* Notify EAPOL state machines that a control response, i.e., user
* input, was received in order to trigger retrying of a pending EAP request.
*/
void eapol_sm_notify_ctrl_response(struct eapol_sm *sm)
{
if (sm == NULL)
return;
if (sm->eapReqData && !sm->eapReq) {
wpa_printf(MSG_DEBUG, "EAPOL: received control response (user "
"input) notification - retrying pending EAP "
"Request");
sm->eapolEap = TRUE;
sm->eapReq = TRUE;
eapol_sm_step(sm);
}
}
/**
* eapol_sm_request_reauth - Request reauthentication
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* This function can be used to request EAPOL reauthentication, e.g., when the
* current PMKSA entry is nearing expiration.
*/
void eapol_sm_request_reauth(struct eapol_sm *sm)
{
if (sm == NULL || sm->SUPP_PAE_state != SUPP_PAE_AUTHENTICATED)
return;
eapol_sm_txStart(sm);
}
/**
* eapol_sm_notify_lower_layer_success - Notification of lower layer success
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
* @in_eapol_sm: Whether the caller is already running inside EAPOL state
* machine loop (eapol_sm_step())
*
* Notify EAPOL (and EAP) state machines that a lower layer has detected a
* successful authentication. This is used to recover from dropped EAP-Success
* messages.
*/
void eapol_sm_notify_lower_layer_success(struct eapol_sm *sm, int in_eapol_sm)
{
if (sm == NULL)
return;
eap_notify_lower_layer_success(sm->eap);
if (!in_eapol_sm)
eapol_sm_step(sm);
}
/**
* eapol_sm_invalidate_cached_session - Mark cached EAP session data invalid
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*/
void eapol_sm_invalidate_cached_session(struct eapol_sm *sm)
{
if (sm)
eap_invalidate_cached_session(sm->eap);
}
static struct eap_peer_config * eapol_sm_get_config(void *ctx)
{
struct eapol_sm *sm = ctx;
return sm ? sm->config : NULL;
}
static struct wpabuf * eapol_sm_get_eapReqData(void *ctx)
{
struct eapol_sm *sm = ctx;
if (sm == NULL || sm->eapReqData == NULL)
return NULL;
return sm->eapReqData;
}
static Boolean eapol_sm_get_bool(void *ctx, enum eapol_bool_var variable)
{
struct eapol_sm *sm = ctx;
if (sm == NULL)
return FALSE;
switch (variable) {
case EAPOL_eapSuccess:
return sm->eapSuccess;
case EAPOL_eapRestart:
return sm->eapRestart;
case EAPOL_eapFail:
return sm->eapFail;
case EAPOL_eapResp:
return sm->eapResp;
case EAPOL_eapNoResp:
return sm->eapNoResp;
case EAPOL_eapReq:
return sm->eapReq;
case EAPOL_portEnabled:
return sm->portEnabled;
case EAPOL_altAccept:
return sm->altAccept;
case EAPOL_altReject:
return sm->altReject;
case EAPOL_eapTriggerStart:
return sm->eapTriggerStart;
}
return FALSE;
}
static void eapol_sm_set_bool(void *ctx, enum eapol_bool_var variable,
Boolean value)
{
struct eapol_sm *sm = ctx;
if (sm == NULL)
return;
switch (variable) {
case EAPOL_eapSuccess:
sm->eapSuccess = value;
break;
case EAPOL_eapRestart:
sm->eapRestart = value;
break;
case EAPOL_eapFail:
sm->eapFail = value;
break;
case EAPOL_eapResp:
sm->eapResp = value;
break;
case EAPOL_eapNoResp:
sm->eapNoResp = value;
break;
case EAPOL_eapReq:
sm->eapReq = value;
break;
case EAPOL_portEnabled:
sm->portEnabled = value;
break;
case EAPOL_altAccept:
sm->altAccept = value;
break;
case EAPOL_altReject:
sm->altReject = value;
break;
case EAPOL_eapTriggerStart:
sm->eapTriggerStart = value;
break;
}
}
static unsigned int eapol_sm_get_int(void *ctx, enum eapol_int_var variable)
{
struct eapol_sm *sm = ctx;
if (sm == NULL)
return 0;
switch (variable) {
case EAPOL_idleWhile:
return sm->idleWhile;
}
return 0;
}
static void eapol_sm_set_int(void *ctx, enum eapol_int_var variable,
unsigned int value)
{
struct eapol_sm *sm = ctx;
if (sm == NULL)
return;
switch (variable) {
case EAPOL_idleWhile:
sm->idleWhile = value;
if (sm->idleWhile > 0)
eapol_enable_timer_tick(sm);
break;
}
}
static void eapol_sm_set_config_blob(void *ctx, struct wpa_config_blob *blob)
{
#ifndef CONFIG_NO_CONFIG_BLOBS
struct eapol_sm *sm = ctx;
if (sm && sm->ctx && sm->ctx->set_config_blob)
sm->ctx->set_config_blob(sm->ctx->ctx, blob);
#endif /* CONFIG_NO_CONFIG_BLOBS */
}
static const struct wpa_config_blob *
eapol_sm_get_config_blob(void *ctx, const char *name)
{
#ifndef CONFIG_NO_CONFIG_BLOBS
struct eapol_sm *sm = ctx;
if (sm && sm->ctx && sm->ctx->get_config_blob)
return sm->ctx->get_config_blob(sm->ctx->ctx, name);
else
return NULL;
#else /* CONFIG_NO_CONFIG_BLOBS */
return NULL;
#endif /* CONFIG_NO_CONFIG_BLOBS */
}
static void eapol_sm_notify_pending(void *ctx)
{
struct eapol_sm *sm = ctx;
if (sm == NULL)
return;
if (sm->eapReqData && !sm->eapReq) {
wpa_printf(MSG_DEBUG, "EAPOL: received notification from EAP "
"state machine - retrying pending EAP Request");
sm->eapolEap = TRUE;
sm->eapReq = TRUE;
eapol_sm_step(sm);
}
}
#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG)
static void eapol_sm_eap_param_needed(void *ctx, enum wpa_ctrl_req_type field,
const char *txt)
{
struct eapol_sm *sm = ctx;
wpa_printf(MSG_DEBUG, "EAPOL: EAP parameter needed");
if (sm->ctx->eap_param_needed)
sm->ctx->eap_param_needed(sm->ctx->ctx, field, txt);
}
#else /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */
#define eapol_sm_eap_param_needed NULL
#endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */
static void eapol_sm_notify_cert(void *ctx, int depth, const char *subject,
const char *altsubject[],
int num_altsubject, const char *cert_hash,
const struct wpabuf *cert)
{
struct eapol_sm *sm = ctx;
if (sm->ctx->cert_cb)
sm->ctx->cert_cb(sm->ctx->ctx, depth, subject, altsubject,
num_altsubject, cert_hash, cert);
}
static void eapol_sm_notify_status(void *ctx, const char *status,
const char *parameter)
{
struct eapol_sm *sm = ctx;
if (sm->ctx->status_cb)
sm->ctx->status_cb(sm->ctx->ctx, status, parameter);
}
#ifdef CONFIG_EAP_PROXY
static void eapol_sm_eap_proxy_cb(void *ctx)
{
struct eapol_sm *sm = ctx;
if (sm->ctx->eap_proxy_cb)
sm->ctx->eap_proxy_cb(sm->ctx->ctx);
}
#endif /* CONFIG_EAP_PROXY */
static void eapol_sm_set_anon_id(void *ctx, const u8 *id, size_t len)
{
struct eapol_sm *sm = ctx;
if (sm->ctx->set_anon_id)
sm->ctx->set_anon_id(sm->ctx->ctx, id, len);
}
static const struct eapol_callbacks eapol_cb =
{
eapol_sm_get_config,
eapol_sm_get_bool,
eapol_sm_set_bool,
eapol_sm_get_int,
eapol_sm_set_int,
eapol_sm_get_eapReqData,
eapol_sm_set_config_blob,
eapol_sm_get_config_blob,
eapol_sm_notify_pending,
eapol_sm_eap_param_needed,
eapol_sm_notify_cert,
eapol_sm_notify_status,
#ifdef CONFIG_EAP_PROXY
eapol_sm_eap_proxy_cb,
#endif /* CONFIG_EAP_PROXY */
eapol_sm_set_anon_id
};
/**
* eapol_sm_init - Initialize EAPOL state machine
* @ctx: Pointer to EAPOL context data; this needs to be an allocated buffer
* and EAPOL state machine will free it in eapol_sm_deinit()
* Returns: Pointer to the allocated EAPOL state machine or %NULL on failure
*
* Allocate and initialize an EAPOL state machine.
*/
struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx)
{
struct eapol_sm *sm;
struct eap_config conf;
sm = os_zalloc(sizeof(*sm));
if (sm == NULL)
return NULL;
sm->ctx = ctx;
sm->portControl = Auto;
/* Supplicant PAE state machine */
sm->heldPeriod = 60;
sm->startPeriod = 30;
sm->maxStart = 3;
/* Supplicant Backend state machine */
sm->authPeriod = 30;
os_memset(&conf, 0, sizeof(conf));
conf.opensc_engine_path = ctx->opensc_engine_path;
conf.pkcs11_engine_path = ctx->pkcs11_engine_path;
conf.pkcs11_module_path = ctx->pkcs11_module_path;
conf.openssl_ciphers = ctx->openssl_ciphers;
conf.wps = ctx->wps;
conf.cert_in_cb = ctx->cert_in_cb;
sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf);
if (sm->eap == NULL) {
os_free(sm);
return NULL;
}
#ifdef CONFIG_EAP_PROXY
sm->use_eap_proxy = FALSE;
sm->eap_proxy = eap_proxy_init(sm, &eapol_cb, sm->ctx->msg_ctx);
if (sm->eap_proxy == NULL) {
wpa_printf(MSG_ERROR, "Unable to initialize EAP Proxy");
}
#endif /* CONFIG_EAP_PROXY */
/* Initialize EAPOL state machines */
sm->force_authorized_update = TRUE;
sm->initialize = TRUE;
eapol_sm_step(sm);
sm->initialize = FALSE;
eapol_sm_step(sm);
sm->timer_tick_enabled = 1;
eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);
return sm;
}
/**
* eapol_sm_deinit - Deinitialize EAPOL state machine
* @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
*
* Deinitialize and free EAPOL state machine.
*/
void eapol_sm_deinit(struct eapol_sm *sm)
{
if (sm == NULL)
return;
eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm);
eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm);
eap_peer_sm_deinit(sm->eap);
#ifdef CONFIG_EAP_PROXY
eap_proxy_deinit(sm->eap_proxy);
#endif /* CONFIG_EAP_PROXY */
os_free(sm->last_rx_key);
wpabuf_free(sm->eapReqData);
os_free(sm->ctx);
os_free(sm);
}
void eapol_sm_set_ext_pw_ctx(struct eapol_sm *sm,
struct ext_password_data *ext)
{
if (sm && sm->eap)
eap_sm_set_ext_pw_ctx(sm->eap, ext);
}
int eapol_sm_failed(struct eapol_sm *sm)
{
if (sm == NULL)
return 0;
return !sm->eapSuccess && sm->eapFail;
}
int eapol_sm_get_eap_proxy_imsi(struct eapol_sm *sm, char *imsi, size_t *len)
{
#ifdef CONFIG_EAP_PROXY
if (sm->eap_proxy == NULL)
return -1;
return eap_proxy_get_imsi(sm->eap_proxy, imsi, len);
#else /* CONFIG_EAP_PROXY */
return -1;
#endif /* CONFIG_EAP_PROXY */
}
void eapol_sm_erp_flush(struct eapol_sm *sm)
{
if (sm)
eap_peer_erp_free_keys(sm->eap);
}