/*
* WPA Supplicant / EAPOL state machines
*
* 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_sm.h"
#include "eap.h"
#include "eloop.h"
#include "l2_packet.h"
#include "wpa.h"
#include "md5.h"
#include "rc4.h"
#include "state_machine.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 */
/* 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 wpa_ssid *config;
Boolean initial_req;
u8 *last_rx_key;
size_t last_rx_key_len;
u8 *eapReqData; /* for EAP */
size_t eapReqDataLen; /* 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");
}
eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx, sm);
eapol_sm_step(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;
}
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;
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