| /* |
| * RSN pre-authentication (supplicant) |
| * Copyright (c) 2003-2015, 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 "wpa.h" |
| #include "eloop.h" |
| #include "l2_packet/l2_packet.h" |
| #include "eapol_supp/eapol_supp_sm.h" |
| #include "preauth.h" |
| #include "pmksa_cache.h" |
| #include "wpa_i.h" |
| |
| |
| #if defined(IEEE8021X_EAPOL) && !defined(CONFIG_NO_WPA) |
| |
| #define PMKID_CANDIDATE_PRIO_SCAN 1000 |
| |
| |
| struct rsn_pmksa_candidate { |
| struct dl_list list; |
| u8 bssid[ETH_ALEN]; |
| int priority; |
| }; |
| |
| |
| /** |
| * pmksa_candidate_free - Free all entries in PMKSA candidate list |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| */ |
| void pmksa_candidate_free(struct wpa_sm *sm) |
| { |
| struct rsn_pmksa_candidate *entry, *n; |
| |
| if (sm == NULL) |
| return; |
| |
| dl_list_for_each_safe(entry, n, &sm->pmksa_candidates, |
| struct rsn_pmksa_candidate, list) { |
| dl_list_del(&entry->list); |
| os_free(entry); |
| } |
| } |
| |
| |
| static int rsn_preauth_key_mgmt(int akmp) |
| { |
| return !!(akmp & (WPA_KEY_MGMT_IEEE8021X | |
| WPA_KEY_MGMT_IEEE8021X_SHA256 | |
| WPA_KEY_MGMT_IEEE8021X_SUITE_B | |
| WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)); |
| } |
| |
| |
| static void rsn_preauth_receive(void *ctx, const u8 *src_addr, |
| const u8 *buf, size_t len) |
| { |
| struct wpa_sm *sm = ctx; |
| |
| wpa_printf(MSG_DEBUG, "RX pre-auth from " MACSTR, MAC2STR(src_addr)); |
| wpa_hexdump(MSG_MSGDUMP, "RX pre-auth", buf, len); |
| |
| if (sm->preauth_eapol == NULL || |
| is_zero_ether_addr(sm->preauth_bssid) || |
| os_memcmp(sm->preauth_bssid, src_addr, ETH_ALEN) != 0) { |
| wpa_printf(MSG_WARNING, "RSN pre-auth frame received from " |
| "unexpected source " MACSTR " - dropped", |
| MAC2STR(src_addr)); |
| return; |
| } |
| |
| eapol_sm_rx_eapol(sm->preauth_eapol, src_addr, buf, len, |
| FRAME_ENCRYPTION_UNKNOWN); |
| } |
| |
| |
| static void rsn_preauth_eapol_cb(struct eapol_sm *eapol, |
| enum eapol_supp_result result, |
| void *ctx) |
| { |
| struct wpa_sm *sm = ctx; |
| u8 pmk[PMK_LEN]; |
| |
| if (result == EAPOL_SUPP_RESULT_SUCCESS) { |
| int res, pmk_len; |
| pmk_len = PMK_LEN; |
| res = eapol_sm_get_key(eapol, pmk, PMK_LEN); |
| if (res) { |
| /* |
| * EAP-LEAP is an exception from other EAP methods: it |
| * uses only 16-byte PMK. |
| */ |
| res = eapol_sm_get_key(eapol, pmk, 16); |
| pmk_len = 16; |
| } |
| if (res == 0) { |
| wpa_hexdump_key(MSG_DEBUG, "RSN: PMK from pre-auth", |
| pmk, pmk_len); |
| sm->pmk_len = pmk_len; |
| pmksa_cache_add(sm->pmksa, pmk, pmk_len, NULL, |
| NULL, 0, |
| sm->preauth_bssid, sm->own_addr, |
| sm->network_ctx, |
| WPA_KEY_MGMT_IEEE8021X, NULL); |
| } else { |
| wpa_msg(sm->ctx->msg_ctx, MSG_INFO, |
| "RSN: failed to get master session key from " |
| "pre-auth EAPOL state machines"); |
| result = EAPOL_SUPP_RESULT_FAILURE; |
| } |
| } |
| |
| wpa_msg(sm->ctx->msg_ctx, MSG_INFO, "RSN: pre-authentication with " |
| MACSTR " %s", MAC2STR(sm->preauth_bssid), |
| result == EAPOL_SUPP_RESULT_SUCCESS ? "completed successfully" : |
| "failed"); |
| |
| rsn_preauth_deinit(sm); |
| rsn_preauth_candidate_process(sm); |
| } |
| |
| |
| static void rsn_preauth_timeout(void *eloop_ctx, void *timeout_ctx) |
| { |
| struct wpa_sm *sm = eloop_ctx; |
| |
| wpa_msg(sm->ctx->msg_ctx, MSG_INFO, "RSN: pre-authentication with " |
| MACSTR " timed out", MAC2STR(sm->preauth_bssid)); |
| rsn_preauth_deinit(sm); |
| rsn_preauth_candidate_process(sm); |
| } |
| |
| |
| static int rsn_preauth_eapol_send(void *ctx, int type, const u8 *buf, |
| size_t len) |
| { |
| struct wpa_sm *sm = ctx; |
| u8 *msg; |
| size_t msglen; |
| int res; |
| |
| /* TODO: could add l2_packet_sendmsg that allows fragments to avoid |
| * extra copy here */ |
| |
| if (sm->l2_preauth == NULL) |
| return -1; |
| |
| msg = wpa_sm_alloc_eapol(sm, type, buf, len, &msglen, NULL); |
| if (msg == NULL) |
| return -1; |
| |
| wpa_hexdump(MSG_MSGDUMP, "TX EAPOL (preauth)", msg, msglen); |
| res = l2_packet_send(sm->l2_preauth, sm->preauth_bssid, |
| ETH_P_RSN_PREAUTH, msg, msglen); |
| os_free(msg); |
| return res; |
| } |
| |
| |
| /** |
| * rsn_preauth_init - Start new RSN pre-authentication |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| * @dst: Authenticator address (BSSID) with which to preauthenticate |
| * @eap_conf: Current EAP configuration |
| * Returns: 0 on success, -1 on another pre-authentication is in progress, |
| * -2 on layer 2 packet initialization failure, -3 on EAPOL state machine |
| * initialization failure, -4 on memory allocation failure |
| * |
| * This function request an RSN pre-authentication with a given destination |
| * address. This is usually called for PMKSA candidates found from scan results |
| * or from driver reports. In addition, ctrl_iface PREAUTH command can trigger |
| * pre-authentication. |
| */ |
| int rsn_preauth_init(struct wpa_sm *sm, const u8 *dst, |
| struct eap_peer_config *eap_conf) |
| { |
| struct eapol_config eapol_conf; |
| struct eapol_ctx *ctx; |
| int ret; |
| |
| if (sm->preauth_eapol) |
| return -1; |
| |
| wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, |
| "RSN: starting pre-authentication with " MACSTR, MAC2STR(dst)); |
| |
| sm->l2_preauth = l2_packet_init(sm->ifname, sm->own_addr, |
| ETH_P_RSN_PREAUTH, |
| rsn_preauth_receive, sm, 0); |
| if (sm->l2_preauth == NULL) { |
| wpa_printf(MSG_WARNING, "RSN: Failed to initialize L2 packet " |
| "processing for pre-authentication"); |
| return -2; |
| } |
| |
| if (sm->bridge_ifname) { |
| sm->l2_preauth_br = l2_packet_init(sm->bridge_ifname, |
| sm->own_addr, |
| ETH_P_RSN_PREAUTH, |
| rsn_preauth_receive, sm, 0); |
| if (sm->l2_preauth_br == NULL) { |
| wpa_printf(MSG_WARNING, "RSN: Failed to initialize L2 " |
| "packet processing (bridge) for " |
| "pre-authentication"); |
| ret = -2; |
| goto fail; |
| } |
| } |
| |
| ctx = os_zalloc(sizeof(*ctx)); |
| if (ctx == NULL) { |
| wpa_printf(MSG_WARNING, "Failed to allocate EAPOL context."); |
| ret = -4; |
| goto fail; |
| } |
| ctx->ctx = sm->ctx->ctx; |
| ctx->msg_ctx = sm->ctx->ctx; |
| ctx->preauth = 1; |
| ctx->cb = rsn_preauth_eapol_cb; |
| ctx->cb_ctx = sm; |
| ctx->scard_ctx = sm->scard_ctx; |
| ctx->eapol_send = rsn_preauth_eapol_send; |
| ctx->eapol_send_ctx = sm; |
| ctx->set_config_blob = sm->ctx->set_config_blob; |
| ctx->get_config_blob = sm->ctx->get_config_blob; |
| |
| sm->preauth_eapol = eapol_sm_init(ctx); |
| if (sm->preauth_eapol == NULL) { |
| os_free(ctx); |
| wpa_printf(MSG_WARNING, "RSN: Failed to initialize EAPOL " |
| "state machines for pre-authentication"); |
| ret = -3; |
| goto fail; |
| } |
| os_memset(&eapol_conf, 0, sizeof(eapol_conf)); |
| eapol_conf.accept_802_1x_keys = 0; |
| eapol_conf.required_keys = 0; |
| eapol_conf.fast_reauth = sm->fast_reauth; |
| eapol_conf.workaround = sm->eap_workaround; |
| eapol_sm_notify_config(sm->preauth_eapol, eap_conf, &eapol_conf); |
| /* |
| * Use a shorter startPeriod with preauthentication since the first |
| * preauth EAPOL-Start frame may end up being dropped due to race |
| * condition in the AP between the data receive and key configuration |
| * after the 4-Way Handshake. |
| */ |
| eapol_sm_configure(sm->preauth_eapol, -1, -1, 5, 6); |
| os_memcpy(sm->preauth_bssid, dst, ETH_ALEN); |
| |
| eapol_sm_notify_portValid(sm->preauth_eapol, true); |
| /* 802.1X::portControl = Auto */ |
| eapol_sm_notify_portEnabled(sm->preauth_eapol, true); |
| |
| eloop_register_timeout(sm->dot11RSNAConfigSATimeout, 0, |
| rsn_preauth_timeout, sm, NULL); |
| |
| return 0; |
| |
| fail: |
| if (sm->l2_preauth_br) { |
| l2_packet_deinit(sm->l2_preauth_br); |
| sm->l2_preauth_br = NULL; |
| } |
| l2_packet_deinit(sm->l2_preauth); |
| sm->l2_preauth = NULL; |
| return ret; |
| } |
| |
| |
| /** |
| * rsn_preauth_deinit - Abort RSN pre-authentication |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| * |
| * This function aborts the current RSN pre-authentication (if one is started) |
| * and frees resources allocated for it. |
| */ |
| void rsn_preauth_deinit(struct wpa_sm *sm) |
| { |
| if (sm == NULL || !sm->preauth_eapol) |
| return; |
| |
| eloop_cancel_timeout(rsn_preauth_timeout, sm, NULL); |
| eapol_sm_deinit(sm->preauth_eapol); |
| sm->preauth_eapol = NULL; |
| os_memset(sm->preauth_bssid, 0, ETH_ALEN); |
| |
| l2_packet_deinit(sm->l2_preauth); |
| sm->l2_preauth = NULL; |
| if (sm->l2_preauth_br) { |
| l2_packet_deinit(sm->l2_preauth_br); |
| sm->l2_preauth_br = NULL; |
| } |
| } |
| |
| |
| /** |
| * rsn_preauth_candidate_process - Process PMKSA candidates |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| * |
| * Go through the PMKSA candidates and start pre-authentication if a candidate |
| * without an existing PMKSA cache entry is found. Processed candidates will be |
| * removed from the list. |
| */ |
| void rsn_preauth_candidate_process(struct wpa_sm *sm) |
| { |
| struct rsn_pmksa_candidate *candidate, *n; |
| |
| if (dl_list_empty(&sm->pmksa_candidates)) |
| return; |
| |
| /* TODO: drop priority for old candidate entries */ |
| |
| wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: processing PMKSA candidate " |
| "list"); |
| if (sm->preauth_eapol || |
| sm->proto != WPA_PROTO_RSN || |
| wpa_sm_get_state(sm) != WPA_COMPLETED || |
| !rsn_preauth_key_mgmt(sm->key_mgmt)) { |
| wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: not in suitable " |
| "state for new pre-authentication"); |
| return; /* invalid state for new pre-auth */ |
| } |
| |
| dl_list_for_each_safe(candidate, n, &sm->pmksa_candidates, |
| struct rsn_pmksa_candidate, list) { |
| struct rsn_pmksa_cache_entry *p = NULL; |
| p = pmksa_cache_get(sm->pmksa, candidate->bssid, sm->own_addr, |
| NULL, NULL, 0); |
| if (os_memcmp(sm->bssid, candidate->bssid, ETH_ALEN) != 0 && |
| (p == NULL || p->opportunistic)) { |
| wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: PMKSA " |
| "candidate " MACSTR |
| " selected for pre-authentication", |
| MAC2STR(candidate->bssid)); |
| dl_list_del(&candidate->list); |
| rsn_preauth_init(sm, candidate->bssid, |
| sm->eap_conf_ctx); |
| os_free(candidate); |
| return; |
| } |
| wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: PMKSA candidate " |
| MACSTR " does not need pre-authentication anymore", |
| MAC2STR(candidate->bssid)); |
| /* Some drivers (e.g., NDIS) expect to get notified about the |
| * PMKIDs again, so report the existing data now. */ |
| if (p) { |
| wpa_sm_add_pmkid(sm, NULL, candidate->bssid, p->pmkid, |
| NULL, p->pmk, p->pmk_len, 0, 0, |
| p->akmp); |
| } |
| |
| dl_list_del(&candidate->list); |
| os_free(candidate); |
| } |
| wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: no more pending PMKSA " |
| "candidates"); |
| } |
| |
| |
| /** |
| * pmksa_candidate_add - Add a new PMKSA candidate |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| * @bssid: BSSID (authenticator address) of the candidate |
| * @prio: Priority (the smaller number, the higher priority) |
| * @preauth: Whether the candidate AP advertises support for pre-authentication |
| * |
| * This function is used to add PMKSA candidates for RSN pre-authentication. It |
| * is called from scan result processing and from driver events for PMKSA |
| * candidates, i.e., EVENT_PMKID_CANDIDATE events to wpa_supplicant_event(). |
| */ |
| void pmksa_candidate_add(struct wpa_sm *sm, const u8 *bssid, |
| int prio, int preauth) |
| { |
| struct rsn_pmksa_candidate *cand, *pos; |
| |
| if (sm->network_ctx && sm->proactive_key_caching) |
| pmksa_cache_get_opportunistic(sm->pmksa, sm->network_ctx, |
| bssid, 0); |
| |
| if (!preauth) { |
| wpa_printf(MSG_DEBUG, "RSN: Ignored PMKID candidate without " |
| "preauth flag"); |
| return; |
| } |
| |
| /* If BSSID already on candidate list, update the priority of the old |
| * entry. Do not override priority based on normal scan results. */ |
| cand = NULL; |
| dl_list_for_each(pos, &sm->pmksa_candidates, |
| struct rsn_pmksa_candidate, list) { |
| if (os_memcmp(pos->bssid, bssid, ETH_ALEN) == 0) { |
| cand = pos; |
| break; |
| } |
| } |
| |
| if (cand) { |
| dl_list_del(&cand->list); |
| if (prio < PMKID_CANDIDATE_PRIO_SCAN) |
| cand->priority = prio; |
| } else { |
| cand = os_zalloc(sizeof(*cand)); |
| if (cand == NULL) |
| return; |
| os_memcpy(cand->bssid, bssid, ETH_ALEN); |
| cand->priority = prio; |
| } |
| |
| /* Add candidate to the list; order by increasing priority value. i.e., |
| * highest priority (smallest value) first. */ |
| dl_list_for_each(pos, &sm->pmksa_candidates, |
| struct rsn_pmksa_candidate, list) { |
| if (cand->priority <= pos->priority) { |
| if (!pos->list.prev) { |
| /* |
| * This cannot really happen in pracrice since |
| * pos was fetched from the list and the prev |
| * pointer must be set. It looks like clang |
| * static analyzer gets confused with the |
| * dl_list_del(&cand->list) call above and ends |
| * up assuming pos->list.prev could be NULL. |
| */ |
| os_free(cand); |
| return; |
| } |
| dl_list_add(pos->list.prev, &cand->list); |
| cand = NULL; |
| break; |
| } |
| } |
| if (cand) |
| dl_list_add_tail(&sm->pmksa_candidates, &cand->list); |
| |
| wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: added PMKSA cache " |
| "candidate " MACSTR " prio %d", MAC2STR(bssid), prio); |
| rsn_preauth_candidate_process(sm); |
| } |
| |
| |
| /* TODO: schedule periodic scans if current AP supports preauth */ |
| |
| /** |
| * rsn_preauth_scan_results - Start processing scan results for canditates |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| * Returns: 0 if ready to process results or -1 to skip processing |
| * |
| * This functions is used to notify RSN code about start of new scan results |
| * processing. The actual scan results will be provided by calling |
| * rsn_preauth_scan_result() for each BSS if this function returned 0. |
| */ |
| int rsn_preauth_scan_results(struct wpa_sm *sm) |
| { |
| if (sm->ssid_len == 0) |
| return -1; |
| |
| /* |
| * TODO: is it ok to free all candidates? What about the entries |
| * received from EVENT_PMKID_CANDIDATE? |
| */ |
| pmksa_candidate_free(sm); |
| |
| return 0; |
| } |
| |
| |
| /** |
| * rsn_preauth_scan_result - Processing scan result for PMKSA canditates |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| * |
| * Add all suitable APs (Authenticators) from scan results into PMKSA |
| * candidate list. |
| */ |
| void rsn_preauth_scan_result(struct wpa_sm *sm, const u8 *bssid, |
| const u8 *ssid, const u8 *rsn) |
| { |
| struct wpa_ie_data ie; |
| struct rsn_pmksa_cache_entry *pmksa; |
| |
| if (ssid[1] != sm->ssid_len || |
| os_memcmp(ssid + 2, sm->ssid, sm->ssid_len) != 0) |
| return; /* Not for the current SSID */ |
| |
| if (os_memcmp(bssid, sm->bssid, ETH_ALEN) == 0) |
| return; /* Ignore current AP */ |
| |
| if (wpa_parse_wpa_ie(rsn, 2 + rsn[1], &ie)) |
| return; |
| |
| pmksa = pmksa_cache_get(sm->pmksa, bssid, sm->own_addr, NULL, NULL, 0); |
| if (pmksa && (!pmksa->opportunistic || |
| !(ie.capabilities & WPA_CAPABILITY_PREAUTH))) |
| return; |
| |
| if (!rsn_preauth_key_mgmt(ie.key_mgmt)) |
| return; |
| |
| /* Give less priority to candidates found from normal scan results. */ |
| pmksa_candidate_add(sm, bssid, PMKID_CANDIDATE_PRIO_SCAN, |
| ie.capabilities & WPA_CAPABILITY_PREAUTH); |
| } |
| |
| |
| #ifdef CONFIG_CTRL_IFACE |
| /** |
| * rsn_preauth_get_status - Get pre-authentication status |
| * @sm: Pointer to WPA state machine data from wpa_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 WPA2 pre-authentication for status information. This function fills in |
| * a text area with current status information. If the buffer (buf) is not |
| * large enough, status information will be truncated to fit the buffer. |
| */ |
| int rsn_preauth_get_status(struct wpa_sm *sm, char *buf, size_t buflen, |
| int verbose) |
| { |
| char *pos = buf, *end = buf + buflen; |
| int res, ret; |
| |
| if (sm->preauth_eapol) { |
| ret = os_snprintf(pos, end - pos, "Pre-authentication " |
| "EAPOL state machines:\n"); |
| if (os_snprintf_error(end - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| res = eapol_sm_get_status(sm->preauth_eapol, |
| pos, end - pos, verbose); |
| if (res >= 0) |
| pos += res; |
| } |
| |
| return pos - buf; |
| } |
| #endif /* CONFIG_CTRL_IFACE */ |
| |
| |
| /** |
| * rsn_preauth_in_progress - Verify whether pre-authentication is in progress |
| * @sm: Pointer to WPA state machine data from wpa_sm_init() |
| */ |
| int rsn_preauth_in_progress(struct wpa_sm *sm) |
| { |
| return sm->preauth_eapol != NULL; |
| } |
| |
| #endif /* IEEE8021X_EAPOL && !CONFIG_NO_WPA */ |