/* * 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 u64 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; bool changed; /* CP -> Client */ bool port_valid; /* Logon -> CP */ enum connect_type connect; /* KaY -> CP */ bool chgd_server; /* clear by CP */ bool elected_self; enum confidentiality_offset cipher_offset; u64 cipher_suite; bool new_sak; /* clear by CP */ struct ieee802_1x_mka_ki distributed_ki; u8 distributed_an; bool using_receive_sas; bool all_receiving; bool server_transmitting; bool using_transmit_sa; /* CP -> KaY */ struct ieee802_1x_mka_ki *lki; u8 lan; bool ltx; bool lrx; struct ieee802_1x_mka_ki *oki; u8 oan; bool otx; bool orx; /* CP -> SecY */ bool protect_frames; enum validate_frames validate_frames; bool replay_protect; u32 replay_window; u64 current_cipher_suite; enum confidentiality_offset confidentiality_offset; bool controlled_port_enabled; /* SecY -> CP */ bool 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 || sm->current_cipher_suite != sm->cipher_suite; } 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); /* The standard doesn't say it but we should clear out the latest * and old key values. Why would we keep advertising them if * they've been deleted and the key server has been changed? */ os_free(sm->oki); sm->oki = NULL; sm->otx = false; sm->orx = false; sm->oan = 0; ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, sm->otx, sm->orx); os_free(sm->lki); sm->lki = NULL; sm->lrx = false; sm->ltx = false; sm->lan = 0; ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, sm->ltx, sm->lrx); } 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_encrypt(sm->kay, sm->kay->macsec_encrypt); 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_encrypt(sm->kay, sm->kay->macsec_encrypt); 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) { SM_ENTRY(CP, SECURED); sm->chgd_server = false; sm->protect_frames = sm->kay->macsec_protect; sm->replay_protect = sm->kay->macsec_replay_protect; sm->validate_frames = sm->kay->macsec_validate; /* NOTE: now no other than default cipher suite (AES-GCM-128) */ sm->current_cipher_suite = sm->cipher_suite; secy_cp_control_current_cipher_suite(sm->kay, sm->current_cipher_suite); 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_encrypt(sm->kay, sm->kay->macsec_encrypt); 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); 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_STATE(CP, RETIRE) { SM_ENTRY(CP, RETIRE); if (sm->oki) { ieee802_1x_kay_delete_sas(sm->kay, sm->oki); os_free(sm->oki); sm->oki = NULL; } sm->oki = sm->lki; sm->otx = sm->ltx; sm->orx = sm->lrx; sm->oan = sm->lan; ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, sm->otx, sm->orx); sm->lki = NULL; sm->ltx = false; sm->lrx = false; sm->lan = 0; ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, sm->ltx, sm->lrx); } /** * 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->controlled_port_enabled || !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, ABANDON); if (sm->server_transmitting || !sm->controlled_port_enabled) 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_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 = kay->macsec_protect; sm->validate_frames = kay->macsec_validate; sm->replay_protect = kay->macsec_replay_protect; sm->replay_window = kay->macsec_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->current_cipher_suite = default_cs_id; sm->cipher_suite = default_cs_id; 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; wpa_printf(MSG_DEBUG, "CP: state machine created"); secy_cp_control_protect_frames(sm->kay, sm->protect_frames); secy_cp_control_encrypt(sm->kay, sm->kay->macsec_encrypt); 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_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); } /** * 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, bool status) { struct ieee802_1x_cp_sm *sm = cp_ctx; sm->elected_self = status; } /** * ieee802_1x_cp_set_ciphersuite - */ void ieee802_1x_cp_set_ciphersuite(void *cp_ctx, u64 cs) { struct ieee802_1x_cp_sm *sm = cp_ctx; sm->cipher_suite = cs; } /** * 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, bool 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, bool 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, bool 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, bool 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); }