hostap/src/eapol_supp/eapol_supp_sm.c
Jouni Malinen ad08c3633c Added preliminary Wi-Fi Protected Setup (WPS) implementation
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.
2008-11-23 19:34:26 +02:00

1860 lines
49 KiB
C

/*
* EAPOL supplicant state machines
* 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"
#include "common.h"
#include "eapol_supp_sm.h"
#include "eap_peer/eap.h"
#include "eloop.h"
#include "eapol_common.h"
#include "md5.h"
#include "rc4.h"
#include "state_machine.h"
#include "wpabuf.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 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;
};
#define IEEE8021X_REPLAY_COUNTER_LEN 8
#define IEEE8021X_KEY_SIGN_LEN 16
#define IEEE8021X_KEY_IV_LEN 16
#define IEEE8021X_KEY_INDEX_FLAG 0x80
#define IEEE8021X_KEY_INDEX_MASK 0x03
#ifdef _MSC_VER
#pragma pack(push, 1)
#endif /* _MSC_VER */
struct ieee802_1x_eapol_key {
u8 type;
/* Note: key_length is unaligned */
u8 key_length[2];
/* does not repeat within the life of the keying material used to
* encrypt the Key field; 64-bit NTP timestamp MAY be used here */
u8 replay_counter[IEEE8021X_REPLAY_COUNTER_LEN];
u8 key_iv[IEEE8021X_KEY_IV_LEN]; /* cryptographically random number */
u8 key_index; /* key flag in the most significant bit:
* 0 = broadcast (default key),
* 1 = unicast (key mapping key); key index is in the
* 7 least significant bits */
/* HMAC-MD5 message integrity check computed with MS-MPPE-Send-Key as
* the key */
u8 key_signature[IEEE8021X_KEY_SIGN_LEN];
/* followed by key: if packet body length = 44 + key length, then the
* key field (of key_length bytes) contains the key in encrypted form;
* if packet body length = 44, key field is absent and key_length
* represents the number of least significant octets from
* MS-MPPE-Send-Key attribute to be used as the keying material;
* RC4 key used in encryption = Key-IV + MS-MPPE-Recv-Key */
} STRUCT_PACKED;
#ifdef _MSC_VER
#pragma pack(pop)
#endif /* _MSC_VER */
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);
/* 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;
sm->suppPortStatus = Unauthorized;
}
SM_STATE(SUPP_PAE, DISCONNECTED)
{
SM_ENTRY(SUPP_PAE, DISCONNECTED);
sm->sPortMode = Auto;
sm->startCount = 0;
sm->logoffSent = FALSE;
sm->suppPortStatus = Unauthorized;
sm->suppAbort = TRUE;
sm->unicast_key_received = FALSE;
sm->broadcast_key_received = FALSE;
}
SM_STATE(SUPP_PAE, CONNECTING)
{
int send_start = sm->SUPP_PAE_state == SUPP_PAE_CONNECTING;
SM_ENTRY(SUPP_PAE, CONNECTING);
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.
*/
sm->startWhen = 3;
}
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);
sm->suppPortStatus = Unauthorized;
sm->cb_status = EAPOL_CB_FAILURE;
}
SM_STATE(SUPP_PAE, AUTHENTICATED)
{
SM_ENTRY(SUPP_PAE, AUTHENTICATED);
sm->suppPortStatus = Authorized;
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);
sm->suppPortStatus = Authorized;
sm->sPortMode = ForceAuthorized;
}
SM_STATE(SUPP_PAE, S_FORCE_UNAUTH)
{
SM_ENTRY(SUPP_PAE, S_FORCE_UNAUTH);
sm->suppPortStatus = Unauthorized;
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);
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;
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;
}
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 conjuction 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)
{
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];
u8 ekey[IEEE8021X_KEY_IV_LEN + IEEE8021X_ENCR_KEY_LEN];
int key_len, res, sign_key_len, encr_key_len;
u16 rx_key_length;
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;
}
hdr = (struct ieee802_1x_hdr *) sm->last_rx_key;
key = (struct ieee802_1x_eapol_key *) (hdr + 1);
if (sizeof(*hdr) + be_to_host16(hdr->length) > sm->last_rx_key_len) {
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(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 = be_to_host16(hdr->length) - 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) {
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(datakey, key_len, ekey,
IEEE8021X_KEY_IV_LEN + encr_key_len);
wpa_hexdump_key(MSG_DEBUG, "EAPOL: Decrypted(RC4) key",
datakey, key_len);
} 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);
}
}
}
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");
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);
}
/**
* 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);
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) {
int success = sm->cb_status == EAPOL_CB_SUCCESS ? 1 : 0;
sm->cb_status = EAPOL_CB_IN_PROGRESS;
sm->ctx->cb(sm, success, 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;
}
#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 (len < 0 || (size_t) len >= buflen)
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 (ret < 0 || (size_t) ret >= buflen - len)
return len;
len += ret;
}
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 (ret < 0 || (size_t) ret >= buflen)
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 (ret < 0 || (size_t) ret >= buflen - len)
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;
}
data_len = plen + sizeof(*hdr);
switch (hdr->type) {
case IEEE802_1X_TYPE_EAP_PACKET:
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;
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;
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);
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;
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;
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);
}
}
/**
* 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;
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;
}
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_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;
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->SUPP_PAE_state = SUPP_PAE_AUTHENTICATED;
sm->suppPortStatus = Authorized;
sm->portValid = 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()
* @attempt: Whether PMKSA caching is tried
*
* Notify EAPOL state machines whether PMKSA caching is used.
*/
void eapol_sm_notify_pmkid_attempt(struct eapol_sm *sm, int attempt)
{
if (sm == NULL)
return;
if (attempt) {
wpa_printf(MSG_DEBUG, "RSN: Trying to use cached PMKSA");
sm->cached_pmk = TRUE;
} else {
wpa_printf(MSG_DEBUG, "RSN: Do not try to use cached PMKSA");
sm->cached_pmk = FALSE;
}
}
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;
sm->suppPortStatus = Unauthorized;
/* 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;
}
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;
}
}
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;
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);
}
}
#ifdef CONFIG_WPS
static int eapol_sm_wps_cred(void *ctx, struct wps_credential *cred)
{
struct eapol_sm *sm = ctx;
wpa_printf(MSG_DEBUG, "EAPOL: received new WPS credential");
if (sm->ctx->wps_cred)
return sm->ctx->wps_cred(sm->ctx->ctx, cred);
return 0;
}
#else /* CONFIG_WPS */
#define eapol_sm_wps_cred NULL
#endif /* CONFIG_WPS */
#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG)
static void eapol_sm_eap_param_needed(void *ctx, const char *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 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_wps_cred,
eapol_sm_eap_param_needed
};
/**
* 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));
#ifdef EAP_TLS_OPENSSL
conf.opensc_engine_path = ctx->opensc_engine_path;
conf.pkcs11_engine_path = ctx->pkcs11_engine_path;
conf.pkcs11_module_path = ctx->pkcs11_module_path;
#endif /* EAP_TLS_OPENSSL */
conf.mac_addr = ctx->mac_addr;
sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf);
if (sm->eap == NULL) {
os_free(sm);
return NULL;
}
/* Initialize EAPOL state machines */
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);
os_free(sm->last_rx_key);
wpabuf_free(sm->eapReqData);
os_free(sm->ctx);
os_free(sm);
}