X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=libeap%2Fsrc%2Fpae%2Fieee802_1x_cp.c;fp=libeap%2Fsrc%2Fpae%2Fieee802_1x_cp.c;h=cf43c594c4022870185bfef9f1b0a0c522e8bd71;hb=4f319dde67a76fe0aaf33f6d2788968012584ada;hp=0000000000000000000000000000000000000000;hpb=ed09b5e64dd485851310307979d5eed14678087b;p=mech_eap.git diff --git a/libeap/src/pae/ieee802_1x_cp.c b/libeap/src/pae/ieee802_1x_cp.c new file mode 100644 index 0000000..cf43c59 --- /dev/null +++ b/libeap/src/pae/ieee802_1x_cp.c @@ -0,0 +1,744 @@ +/* + * IEEE 802.1X-2010 Controlled Port of PAE state machine - CP state machine + * Copyright (c) 2013-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/defs.h" +#include "common/ieee802_1x_defs.h" +#include "utils/state_machine.h" +#include "ieee802_1x_kay.h" +#include "ieee802_1x_secy_ops.h" +#include "pae/ieee802_1x_cp.h" + +#define STATE_MACHINE_DATA struct ieee802_1x_cp_sm +#define STATE_MACHINE_DEBUG_PREFIX "CP" + +static u8 default_cs_id[] = CS_ID_GCM_AES_128; + +/* The variable defined in clause 12 in IEEE Std 802.1X-2010 */ +enum connect_type { PENDING, UNAUTHENTICATED, AUTHENTICATED, SECURE }; + +struct ieee802_1x_cp_sm { + enum cp_states { + CP_BEGIN, CP_INIT, CP_CHANGE, CP_ALLOWED, CP_AUTHENTICATED, + CP_SECURED, CP_RECEIVE, CP_RECEIVING, CP_READY, CP_TRANSMIT, + CP_TRANSMITTING, CP_ABANDON, CP_RETIRE + } CP_state; + Boolean changed; + + /* CP -> Client */ + Boolean port_valid; + + /* Logon -> CP */ + enum connect_type connect; + u8 *authorization_data; + + /* KaY -> CP */ + Boolean chgd_server; /* clear by CP */ + Boolean elected_self; + u8 *authorization_data1; + enum confidentiality_offset cipher_offset; + u8 *cipher_suite; + Boolean new_sak; /* clear by CP */ + struct ieee802_1x_mka_ki distributed_ki; + u8 distributed_an; + Boolean using_receive_sas; + Boolean all_receiving; + Boolean server_transmitting; + Boolean using_transmit_sa; + + /* CP -> KaY */ + struct ieee802_1x_mka_ki *lki; + u8 lan; + Boolean ltx; + Boolean lrx; + struct ieee802_1x_mka_ki *oki; + u8 oan; + Boolean otx; + Boolean orx; + + /* CP -> SecY */ + Boolean protect_frames; + enum validate_frames validate_frames; + + Boolean replay_protect; + u32 replay_window; + + u8 *current_cipher_suite; + enum confidentiality_offset confidentiality_offset; + Boolean controlled_port_enabled; + + /* SecY -> CP */ + Boolean port_enabled; /* SecY->CP */ + + /* private */ + u32 transmit_when; + u32 transmit_delay; + u32 retire_when; + u32 retire_delay; + + /* not defined IEEE Std 802.1X-2010 */ + struct ieee802_1x_kay *kay; +}; + +static void ieee802_1x_cp_retire_when_timeout(void *eloop_ctx, + void *timeout_ctx); +static void ieee802_1x_cp_transmit_when_timeout(void *eloop_ctx, + void *timeout_ctx); + + +static int changed_cipher(struct ieee802_1x_cp_sm *sm) +{ + return sm->confidentiality_offset != sm->cipher_offset || + os_memcmp(sm->current_cipher_suite, sm->cipher_suite, + CS_ID_LEN) != 0; +} + + +static int changed_connect(struct ieee802_1x_cp_sm *sm) +{ + return sm->connect != SECURE || sm->chgd_server || changed_cipher(sm); +} + + +SM_STATE(CP, INIT) +{ + SM_ENTRY(CP, INIT); + + sm->controlled_port_enabled = FALSE; + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + + sm->port_valid = FALSE; + + os_free(sm->lki); + sm->lki = NULL; + sm->ltx = FALSE; + sm->lrx = FALSE; + + os_free(sm->oki); + sm->oki = NULL; + sm->otx = FALSE; + sm->orx = FALSE; + + sm->port_enabled = TRUE; + sm->chgd_server = FALSE; +} + + +SM_STATE(CP, CHANGE) +{ + SM_ENTRY(CP, CHANGE); + + sm->port_valid = FALSE; + sm->controlled_port_enabled = FALSE; + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + + if (sm->lki) + ieee802_1x_kay_delete_sas(sm->kay, sm->lki); + if (sm->oki) + ieee802_1x_kay_delete_sas(sm->kay, sm->oki); +} + + +SM_STATE(CP, ALLOWED) +{ + SM_ENTRY(CP, ALLOWED); + + sm->protect_frames = FALSE; + sm->replay_protect = FALSE; + sm->validate_frames = Checked; + + sm->port_valid = FALSE; + sm->controlled_port_enabled = TRUE; + + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); +} + + +SM_STATE(CP, AUTHENTICATED) +{ + SM_ENTRY(CP, AUTHENTICATED); + + sm->protect_frames = FALSE; + sm->replay_protect = FALSE; + sm->validate_frames = Checked; + + sm->port_valid = FALSE; + sm->controlled_port_enabled = TRUE; + + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); +} + + +SM_STATE(CP, SECURED) +{ + struct ieee802_1x_cp_conf conf; + + SM_ENTRY(CP, SECURED); + + sm->chgd_server = FALSE; + + ieee802_1x_kay_cp_conf(sm->kay, &conf); + sm->protect_frames = conf.protect; + sm->replay_protect = conf.replay_protect; + sm->validate_frames = conf.validate; + + /* NOTE: now no other than default cipher suiter(AES-GCM-128) */ + os_memcpy(sm->current_cipher_suite, sm->cipher_suite, CS_ID_LEN); + secy_cp_control_current_cipher_suite(sm->kay, sm->current_cipher_suite, + CS_ID_LEN); + + sm->confidentiality_offset = sm->cipher_offset; + + sm->port_valid = TRUE; + + secy_cp_control_confidentiality_offset(sm->kay, + sm->confidentiality_offset); + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); +} + + +SM_STATE(CP, RECEIVE) +{ + SM_ENTRY(CP, RECEIVE); + /* RECEIVE state machine not keep with Figure 12-2 in + * IEEE Std 802.1X-2010 */ + sm->oki = sm->lki; + sm->oan = sm->lan; + sm->otx = sm->ltx; + sm->orx = sm->lrx; + ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, + sm->otx, sm->orx); + + sm->lki = os_malloc(sizeof(*sm->lki)); + if (!sm->lki) { + wpa_printf(MSG_ERROR, "CP-%s: Out of memory", __func__); + return; + } + os_memcpy(sm->lki, &sm->distributed_ki, sizeof(*sm->lki)); + sm->lan = sm->distributed_an; + sm->ltx = FALSE; + sm->lrx = FALSE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + ieee802_1x_kay_create_sas(sm->kay, sm->lki); + ieee802_1x_kay_enable_rx_sas(sm->kay, sm->lki); + sm->new_sak = FALSE; + sm->all_receiving = FALSE; +} + + +SM_STATE(CP, RECEIVING) +{ + SM_ENTRY(CP, RECEIVING); + + sm->lrx = TRUE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + sm->transmit_when = sm->transmit_delay; + eloop_cancel_timeout(ieee802_1x_cp_transmit_when_timeout, sm, NULL); + eloop_register_timeout(sm->transmit_when / 1000, 0, + ieee802_1x_cp_transmit_when_timeout, sm, NULL); + /* the electedSelf have been set before CP entering to RECEIVING + * but the CP will transmit from RECEIVING to READY under + * the !electedSelf when KaY is not key server */ + ieee802_1x_cp_sm_step(sm); + sm->using_receive_sas = FALSE; + sm->server_transmitting = FALSE; +} + + +SM_STATE(CP, READY) +{ + SM_ENTRY(CP, READY); + + ieee802_1x_kay_enable_new_info(sm->kay); +} + + +SM_STATE(CP, TRANSMIT) +{ + SM_ENTRY(CP, TRANSMIT); + + sm->controlled_port_enabled = TRUE; + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + sm->ltx = TRUE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + ieee802_1x_kay_enable_tx_sas(sm->kay, sm->lki); + sm->all_receiving = FALSE; + sm->server_transmitting = FALSE; +} + + +SM_STATE(CP, TRANSMITTING) +{ + SM_ENTRY(CP, TRANSMITTING); + sm->retire_when = sm->orx ? sm->retire_delay : 0; + sm->otx = FALSE; + ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, + sm->otx, sm->orx); + ieee802_1x_kay_enable_new_info(sm->kay); + eloop_cancel_timeout(ieee802_1x_cp_retire_when_timeout, sm, NULL); + eloop_register_timeout(sm->retire_when / 1000, 0, + ieee802_1x_cp_retire_when_timeout, sm, NULL); + sm->using_transmit_sa = FALSE; +} + + +SM_STATE(CP, ABANDON) +{ + SM_ENTRY(CP, ABANDON); + sm->lrx = FALSE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + ieee802_1x_kay_delete_sas(sm->kay, sm->lki); + + os_free(sm->lki); + sm->lki = NULL; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + sm->new_sak = FALSE; +} + + +SM_STATE(CP, RETIRE) +{ + SM_ENTRY(CP, RETIRE); + /* RETIRE state machine not keep with Figure 12-2 in + * IEEE Std 802.1X-2010 */ + os_free(sm->oki); + sm->oki = NULL; + sm->orx = FALSE; + sm->otx = FALSE; + ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, + sm->otx, sm->orx); +} + + +/** + * CP state machine handler entry + */ +SM_STEP(CP) +{ + if (!sm->port_enabled) + SM_ENTER(CP, INIT); + + switch (sm->CP_state) { + case CP_BEGIN: + SM_ENTER(CP, INIT); + break; + + case CP_INIT: + SM_ENTER(CP, CHANGE); + break; + + case CP_CHANGE: + if (sm->connect == UNAUTHENTICATED) + SM_ENTER(CP, ALLOWED); + else if (sm->connect == AUTHENTICATED) + SM_ENTER(CP, AUTHENTICATED); + else if (sm->connect == SECURE) + SM_ENTER(CP, SECURED); + break; + + case CP_ALLOWED: + if (sm->connect != UNAUTHENTICATED) + SM_ENTER(CP, CHANGE); + break; + + case CP_AUTHENTICATED: + if (sm->connect != AUTHENTICATED) + SM_ENTER(CP, CHANGE); + break; + + case CP_SECURED: + if (changed_connect(sm)) + SM_ENTER(CP, CHANGE); + else if (sm->new_sak) + SM_ENTER(CP, RECEIVE); + break; + + case CP_RECEIVE: + if (sm->using_receive_sas) + SM_ENTER(CP, RECEIVING); + break; + + case CP_RECEIVING: + if (sm->new_sak || changed_connect(sm)) + SM_ENTER(CP, ABANDON); + if (!sm->elected_self) + SM_ENTER(CP, READY); + if (sm->elected_self && + (sm->all_receiving || !sm->transmit_when)) + SM_ENTER(CP, TRANSMIT); + break; + + case CP_TRANSMIT: + if (sm->using_transmit_sa) + SM_ENTER(CP, TRANSMITTING); + break; + + case CP_TRANSMITTING: + if (!sm->retire_when || changed_connect(sm)) + SM_ENTER(CP, RETIRE); + break; + + case CP_RETIRE: + if (changed_connect(sm)) + SM_ENTER(CP, CHANGE); + else if (sm->new_sak) + SM_ENTER(CP, RECEIVE); + break; + + case CP_READY: + if (sm->new_sak || changed_connect(sm)) + SM_ENTER(CP, RECEIVE); + if (sm->server_transmitting) + SM_ENTER(CP, TRANSMIT); + break; + case CP_ABANDON: + if (changed_connect(sm)) + SM_ENTER(CP, RETIRE); + else if (sm->new_sak) + SM_ENTER(CP, RECEIVE); + break; + default: + wpa_printf(MSG_ERROR, "CP: the state machine is not defined"); + break; + } +} + + +/** + * ieee802_1x_cp_sm_init - + */ +struct ieee802_1x_cp_sm * ieee802_1x_cp_sm_init( + struct ieee802_1x_kay *kay, + struct ieee802_1x_cp_conf *pcp_conf) +{ + struct ieee802_1x_cp_sm *sm; + + sm = os_zalloc(sizeof(*sm)); + if (sm == NULL) { + wpa_printf(MSG_ERROR, "CP-%s: out of memory", __func__); + return NULL; + } + + sm->kay = kay; + + sm->port_valid = FALSE; + + sm->chgd_server = FALSE; + + sm->protect_frames = pcp_conf->protect; + sm->validate_frames = pcp_conf->validate; + sm->replay_protect = pcp_conf->replay_protect; + sm->replay_window = pcp_conf->replay_window; + + sm->controlled_port_enabled = FALSE; + + sm->lki = NULL; + sm->lrx = FALSE; + sm->ltx = FALSE; + sm->oki = NULL; + sm->orx = FALSE; + sm->otx = FALSE; + + sm->cipher_suite = os_zalloc(CS_ID_LEN); + sm->current_cipher_suite = os_zalloc(CS_ID_LEN); + if (!sm->cipher_suite || !sm->current_cipher_suite) { + wpa_printf(MSG_ERROR, "CP-%s: out of memory", __func__); + os_free(sm->cipher_suite); + os_free(sm->current_cipher_suite); + os_free(sm); + return NULL; + } + os_memcpy(sm->current_cipher_suite, default_cs_id, CS_ID_LEN); + os_memcpy(sm->cipher_suite, default_cs_id, CS_ID_LEN); + sm->cipher_offset = CONFIDENTIALITY_OFFSET_0; + sm->confidentiality_offset = sm->cipher_offset; + sm->transmit_delay = MKA_LIFE_TIME; + sm->retire_delay = MKA_SAK_RETIRE_TIME; + sm->CP_state = CP_BEGIN; + sm->changed = FALSE; + sm->authorization_data = NULL; + + wpa_printf(MSG_DEBUG, "CP: state machine created"); + + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + secy_cp_control_confidentiality_offset(sm->kay, + sm->confidentiality_offset); + + SM_ENTER(CP, INIT); + SM_STEP_RUN(CP); + + return sm; +} + + +static void ieee802_1x_cp_step_run(struct ieee802_1x_cp_sm *sm) +{ + enum cp_states prev_state; + int i; + + for (i = 0; i < 100; i++) { + prev_state = sm->CP_state; + SM_STEP_RUN(CP); + if (prev_state == sm->CP_state) + break; + } +} + + +static void ieee802_1x_cp_step_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct ieee802_1x_cp_sm *sm = eloop_ctx; + ieee802_1x_cp_step_run(sm); +} + + +/** + * ieee802_1x_cp_sm_deinit - + */ +void ieee802_1x_cp_sm_deinit(struct ieee802_1x_cp_sm *sm) +{ + wpa_printf(MSG_DEBUG, "CP: state machine removed"); + if (!sm) + return; + + eloop_cancel_timeout(ieee802_1x_cp_retire_when_timeout, sm, NULL); + eloop_cancel_timeout(ieee802_1x_cp_transmit_when_timeout, sm, NULL); + eloop_cancel_timeout(ieee802_1x_cp_step_cb, sm, NULL); + os_free(sm->lki); + os_free(sm->oki); + os_free(sm->cipher_suite); + os_free(sm->current_cipher_suite); + os_free(sm->authorization_data); + os_free(sm); +} + + +/** + * ieee802_1x_cp_connect_pending + */ +void ieee802_1x_cp_connect_pending(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->connect = PENDING; +} + + +/** + * ieee802_1x_cp_connect_unauthenticated + */ +void ieee802_1x_cp_connect_unauthenticated(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = (struct ieee802_1x_cp_sm *)cp_ctx; + + sm->connect = UNAUTHENTICATED; +} + + +/** + * ieee802_1x_cp_connect_authenticated + */ +void ieee802_1x_cp_connect_authenticated(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->connect = AUTHENTICATED; +} + + +/** + * ieee802_1x_cp_connect_secure + */ +void ieee802_1x_cp_connect_secure(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->connect = SECURE; +} + + +/** + * ieee802_1x_cp_set_chgdserver - + */ +void ieee802_1x_cp_signal_chgdserver(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->chgd_server = TRUE; +} + + +/** + * ieee802_1x_cp_set_electedself - + */ +void ieee802_1x_cp_set_electedself(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->elected_self = status; +} + + +/** + * ieee802_1x_cp_set_authorizationdata - + */ +void ieee802_1x_cp_set_authorizationdata(void *cp_ctx, u8 *pdata, int len) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + os_free(sm->authorization_data); + sm->authorization_data = os_zalloc(len); + if (sm->authorization_data) + os_memcpy(sm->authorization_data, pdata, len); +} + + +/** + * ieee802_1x_cp_set_ciphersuite - + */ +void ieee802_1x_cp_set_ciphersuite(void *cp_ctx, void *pid) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + os_memcpy(sm->cipher_suite, pid, CS_ID_LEN); +} + + +/** + * ieee802_1x_cp_set_offset - + */ +void ieee802_1x_cp_set_offset(void *cp_ctx, enum confidentiality_offset offset) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->cipher_offset = offset; +} + + +/** + * ieee802_1x_cp_signal_newsak - + */ +void ieee802_1x_cp_signal_newsak(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->new_sak = TRUE; +} + + +/** + * ieee802_1x_cp_set_distributedki - + */ +void ieee802_1x_cp_set_distributedki(void *cp_ctx, + const struct ieee802_1x_mka_ki *dki) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + os_memcpy(&sm->distributed_ki, dki, sizeof(struct ieee802_1x_mka_ki)); +} + + +/** + * ieee802_1x_cp_set_distributedan - + */ +void ieee802_1x_cp_set_distributedan(void *cp_ctx, u8 an) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->distributed_an = an; +} + + +/** + * ieee802_1x_cp_set_usingreceivesas - + */ +void ieee802_1x_cp_set_usingreceivesas(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->using_receive_sas = status; +} + + +/** + * ieee802_1x_cp_set_allreceiving - + */ +void ieee802_1x_cp_set_allreceiving(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->all_receiving = status; +} + + +/** + * ieee802_1x_cp_set_servertransmitting - + */ +void ieee802_1x_cp_set_servertransmitting(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->server_transmitting = status; +} + + +/** + * ieee802_1x_cp_set_usingtransmitsas - + */ +void ieee802_1x_cp_set_usingtransmitas(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->using_transmit_sa = status; +} + + +/** + * ieee802_1x_cp_sm_step - Advance EAPOL state machines + * @sm: EAPOL state machine + * + * This function is called to advance CP state machines after any change + * that could affect their state. + */ +void ieee802_1x_cp_sm_step(void *cp_ctx) +{ + /* + * Run ieee802_1x_cp_step_run from a registered timeout + * to make sure that other possible timeouts/events are processed + * and to avoid long function call chains. + */ + struct ieee802_1x_cp_sm *sm = cp_ctx; + eloop_cancel_timeout(ieee802_1x_cp_step_cb, sm, NULL); + eloop_register_timeout(0, 0, ieee802_1x_cp_step_cb, sm, NULL); +} + + +static void ieee802_1x_cp_retire_when_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct ieee802_1x_cp_sm *sm = eloop_ctx; + sm->retire_when = 0; + ieee802_1x_cp_step_run(sm); +} + + +static void +ieee802_1x_cp_transmit_when_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct ieee802_1x_cp_sm *sm = eloop_ctx; + sm->transmit_when = 0; + ieee802_1x_cp_step_run(sm); +}