xref: /freebsd/contrib/wpa/src/rsn_supp/pmksa_cache.c (revision 85732ac8bccbc0adcf5a261ea1ffec8ca7b3a92d)
139beb93cSSam Leffler /*
239beb93cSSam Leffler  * WPA Supplicant - RSN PMKSA cache
35b9c547cSRui Paulo  * Copyright (c) 2004-2009, 2011-2015, Jouni Malinen <j@w1.fi>
439beb93cSSam Leffler  *
5f05cddf9SRui Paulo  * This software may be distributed under the terms of the BSD license.
6f05cddf9SRui Paulo  * See README for more details.
739beb93cSSam Leffler  */
839beb93cSSam Leffler 
939beb93cSSam Leffler #include "includes.h"
1039beb93cSSam Leffler 
1139beb93cSSam Leffler #include "common.h"
1239beb93cSSam Leffler #include "eloop.h"
1339beb93cSSam Leffler #include "eapol_supp/eapol_supp_sm.h"
14e28a4053SRui Paulo #include "wpa.h"
15e28a4053SRui Paulo #include "wpa_i.h"
1639beb93cSSam Leffler #include "pmksa_cache.h"
1739beb93cSSam Leffler 
18780fb4a2SCy Schubert #if defined(IEEE8021X_EAPOL) && !defined(CONFIG_NO_WPA)
1939beb93cSSam Leffler 
2039beb93cSSam Leffler static const int pmksa_cache_max_entries = 32;
2139beb93cSSam Leffler 
2239beb93cSSam Leffler struct rsn_pmksa_cache {
2339beb93cSSam Leffler 	struct rsn_pmksa_cache_entry *pmksa; /* PMKSA cache */
2439beb93cSSam Leffler 	int pmksa_count; /* number of entries in PMKSA cache */
2539beb93cSSam Leffler 	struct wpa_sm *sm; /* TODO: get rid of this reference(?) */
2639beb93cSSam Leffler 
2739beb93cSSam Leffler 	void (*free_cb)(struct rsn_pmksa_cache_entry *entry, void *ctx,
28f05cddf9SRui Paulo 			enum pmksa_free_reason reason);
2939beb93cSSam Leffler 	void *ctx;
3039beb93cSSam Leffler };
3139beb93cSSam Leffler 
3239beb93cSSam Leffler 
3339beb93cSSam Leffler static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa);
3439beb93cSSam Leffler 
3539beb93cSSam Leffler 
3639beb93cSSam Leffler static void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry)
3739beb93cSSam Leffler {
385b9c547cSRui Paulo 	bin_clear_free(entry, sizeof(*entry));
3939beb93cSSam Leffler }
4039beb93cSSam Leffler 
4139beb93cSSam Leffler 
4239beb93cSSam Leffler static void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa,
4339beb93cSSam Leffler 				   struct rsn_pmksa_cache_entry *entry,
44f05cddf9SRui Paulo 				   enum pmksa_free_reason reason)
4539beb93cSSam Leffler {
46*85732ac8SCy Schubert 	wpa_sm_remove_pmkid(pmksa->sm, entry->network_ctx, entry->aa,
47*85732ac8SCy Schubert 			    entry->pmkid,
48*85732ac8SCy Schubert 			    entry->fils_cache_id_set ? entry->fils_cache_id :
49*85732ac8SCy Schubert 			    NULL);
5039beb93cSSam Leffler 	pmksa->pmksa_count--;
51f05cddf9SRui Paulo 	pmksa->free_cb(entry, pmksa->ctx, reason);
5239beb93cSSam Leffler 	_pmksa_cache_free_entry(entry);
5339beb93cSSam Leffler }
5439beb93cSSam Leffler 
5539beb93cSSam Leffler 
5639beb93cSSam Leffler static void pmksa_cache_expire(void *eloop_ctx, void *timeout_ctx)
5739beb93cSSam Leffler {
5839beb93cSSam Leffler 	struct rsn_pmksa_cache *pmksa = eloop_ctx;
595b9c547cSRui Paulo 	struct os_reltime now;
6039beb93cSSam Leffler 
615b9c547cSRui Paulo 	os_get_reltime(&now);
6239beb93cSSam Leffler 	while (pmksa->pmksa && pmksa->pmksa->expiration <= now.sec) {
6339beb93cSSam Leffler 		struct rsn_pmksa_cache_entry *entry = pmksa->pmksa;
6439beb93cSSam Leffler 		pmksa->pmksa = entry->next;
6539beb93cSSam Leffler 		wpa_printf(MSG_DEBUG, "RSN: expired PMKSA cache entry for "
6639beb93cSSam Leffler 			   MACSTR, MAC2STR(entry->aa));
67f05cddf9SRui Paulo 		pmksa_cache_free_entry(pmksa, entry, PMKSA_EXPIRE);
6839beb93cSSam Leffler 	}
6939beb93cSSam Leffler 
7039beb93cSSam Leffler 	pmksa_cache_set_expiration(pmksa);
7139beb93cSSam Leffler }
7239beb93cSSam Leffler 
7339beb93cSSam Leffler 
7439beb93cSSam Leffler static void pmksa_cache_reauth(void *eloop_ctx, void *timeout_ctx)
7539beb93cSSam Leffler {
7639beb93cSSam Leffler 	struct rsn_pmksa_cache *pmksa = eloop_ctx;
7739beb93cSSam Leffler 	pmksa->sm->cur_pmksa = NULL;
7839beb93cSSam Leffler 	eapol_sm_request_reauth(pmksa->sm->eapol);
7939beb93cSSam Leffler }
8039beb93cSSam Leffler 
8139beb93cSSam Leffler 
8239beb93cSSam Leffler static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa)
8339beb93cSSam Leffler {
8439beb93cSSam Leffler 	int sec;
8539beb93cSSam Leffler 	struct rsn_pmksa_cache_entry *entry;
865b9c547cSRui Paulo 	struct os_reltime now;
8739beb93cSSam Leffler 
8839beb93cSSam Leffler 	eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL);
8939beb93cSSam Leffler 	eloop_cancel_timeout(pmksa_cache_reauth, pmksa, NULL);
9039beb93cSSam Leffler 	if (pmksa->pmksa == NULL)
9139beb93cSSam Leffler 		return;
925b9c547cSRui Paulo 	os_get_reltime(&now);
9339beb93cSSam Leffler 	sec = pmksa->pmksa->expiration - now.sec;
9439beb93cSSam Leffler 	if (sec < 0)
9539beb93cSSam Leffler 		sec = 0;
9639beb93cSSam Leffler 	eloop_register_timeout(sec + 1, 0, pmksa_cache_expire, pmksa, NULL);
9739beb93cSSam Leffler 
9839beb93cSSam Leffler 	entry = pmksa->sm->cur_pmksa ? pmksa->sm->cur_pmksa :
99*85732ac8SCy Schubert 		pmksa_cache_get(pmksa, pmksa->sm->bssid, NULL, NULL, 0);
10039beb93cSSam Leffler 	if (entry) {
10139beb93cSSam Leffler 		sec = pmksa->pmksa->reauth_time - now.sec;
10239beb93cSSam Leffler 		if (sec < 0)
10339beb93cSSam Leffler 			sec = 0;
10439beb93cSSam Leffler 		eloop_register_timeout(sec, 0, pmksa_cache_reauth, pmksa,
10539beb93cSSam Leffler 				       NULL);
10639beb93cSSam Leffler 	}
10739beb93cSSam Leffler }
10839beb93cSSam Leffler 
10939beb93cSSam Leffler 
11039beb93cSSam Leffler /**
11139beb93cSSam Leffler  * pmksa_cache_add - Add a PMKSA cache entry
11239beb93cSSam Leffler  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init()
11339beb93cSSam Leffler  * @pmk: The new pairwise master key
11439beb93cSSam Leffler  * @pmk_len: PMK length in bytes, usually PMK_LEN (32)
115780fb4a2SCy Schubert  * @pmkid: Calculated PMKID
1165b9c547cSRui Paulo  * @kck: Key confirmation key or %NULL if not yet derived
1175b9c547cSRui Paulo  * @kck_len: KCK length in bytes
11839beb93cSSam Leffler  * @aa: Authenticator address
11939beb93cSSam Leffler  * @spa: Supplicant address
12039beb93cSSam Leffler  * @network_ctx: Network configuration context for this PMK
12139beb93cSSam Leffler  * @akmp: WPA_KEY_MGMT_* used in key derivation
122*85732ac8SCy Schubert  * @cache_id: Pointer to FILS Cache Identifier or %NULL if not advertised
12339beb93cSSam Leffler  * Returns: Pointer to the added PMKSA cache entry or %NULL on error
12439beb93cSSam Leffler  *
12539beb93cSSam Leffler  * This function create a PMKSA entry for a new PMK and adds it to the PMKSA
12639beb93cSSam Leffler  * cache. If an old entry is already in the cache for the same Authenticator,
12739beb93cSSam Leffler  * this entry will be replaced with the new entry. PMKID will be calculated
12839beb93cSSam Leffler  * based on the PMK and the driver interface is notified of the new PMKID.
12939beb93cSSam Leffler  */
13039beb93cSSam Leffler struct rsn_pmksa_cache_entry *
13139beb93cSSam Leffler pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len,
132780fb4a2SCy Schubert 		const u8 *pmkid, const u8 *kck, size_t kck_len,
133*85732ac8SCy Schubert 		const u8 *aa, const u8 *spa, void *network_ctx, int akmp,
134*85732ac8SCy Schubert 		const u8 *cache_id)
13539beb93cSSam Leffler {
136*85732ac8SCy Schubert 	struct rsn_pmksa_cache_entry *entry;
1375b9c547cSRui Paulo 	struct os_reltime now;
13839beb93cSSam Leffler 
139780fb4a2SCy Schubert 	if (pmk_len > PMK_LEN_MAX)
14039beb93cSSam Leffler 		return NULL;
14139beb93cSSam Leffler 
1425b9c547cSRui Paulo 	if (wpa_key_mgmt_suite_b(akmp) && !kck)
1435b9c547cSRui Paulo 		return NULL;
1445b9c547cSRui Paulo 
14539beb93cSSam Leffler 	entry = os_zalloc(sizeof(*entry));
14639beb93cSSam Leffler 	if (entry == NULL)
14739beb93cSSam Leffler 		return NULL;
14839beb93cSSam Leffler 	os_memcpy(entry->pmk, pmk, pmk_len);
14939beb93cSSam Leffler 	entry->pmk_len = pmk_len;
150780fb4a2SCy Schubert 	if (pmkid)
151780fb4a2SCy Schubert 		os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
152780fb4a2SCy Schubert 	else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
1535b9c547cSRui Paulo 		rsn_pmkid_suite_b_192(kck, kck_len, aa, spa, entry->pmkid);
1545b9c547cSRui Paulo 	else if (wpa_key_mgmt_suite_b(akmp))
1555b9c547cSRui Paulo 		rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid);
1565b9c547cSRui Paulo 	else
157*85732ac8SCy Schubert 		rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, akmp);
1585b9c547cSRui Paulo 	os_get_reltime(&now);
15939beb93cSSam Leffler 	entry->expiration = now.sec + pmksa->sm->dot11RSNAConfigPMKLifetime;
16039beb93cSSam Leffler 	entry->reauth_time = now.sec + pmksa->sm->dot11RSNAConfigPMKLifetime *
16139beb93cSSam Leffler 		pmksa->sm->dot11RSNAConfigPMKReauthThreshold / 100;
16239beb93cSSam Leffler 	entry->akmp = akmp;
163*85732ac8SCy Schubert 	if (cache_id) {
164*85732ac8SCy Schubert 		entry->fils_cache_id_set = 1;
165*85732ac8SCy Schubert 		os_memcpy(entry->fils_cache_id, cache_id, FILS_CACHE_ID_LEN);
166*85732ac8SCy Schubert 	}
16739beb93cSSam Leffler 	os_memcpy(entry->aa, aa, ETH_ALEN);
16839beb93cSSam Leffler 	entry->network_ctx = network_ctx;
16939beb93cSSam Leffler 
170*85732ac8SCy Schubert 	return pmksa_cache_add_entry(pmksa, entry);
171*85732ac8SCy Schubert }
172*85732ac8SCy Schubert 
173*85732ac8SCy Schubert 
174*85732ac8SCy Schubert struct rsn_pmksa_cache_entry *
175*85732ac8SCy Schubert pmksa_cache_add_entry(struct rsn_pmksa_cache *pmksa,
176*85732ac8SCy Schubert 		      struct rsn_pmksa_cache_entry *entry)
177*85732ac8SCy Schubert {
178*85732ac8SCy Schubert 	struct rsn_pmksa_cache_entry *pos, *prev;
179*85732ac8SCy Schubert 
18039beb93cSSam Leffler 	/* Replace an old entry for the same Authenticator (if found) with the
18139beb93cSSam Leffler 	 * new entry */
18239beb93cSSam Leffler 	pos = pmksa->pmksa;
18339beb93cSSam Leffler 	prev = NULL;
18439beb93cSSam Leffler 	while (pos) {
185*85732ac8SCy Schubert 		if (os_memcmp(entry->aa, pos->aa, ETH_ALEN) == 0) {
186*85732ac8SCy Schubert 			if (pos->pmk_len == entry->pmk_len &&
187*85732ac8SCy Schubert 			    os_memcmp_const(pos->pmk, entry->pmk,
188*85732ac8SCy Schubert 					    entry->pmk_len) == 0 &&
1895b9c547cSRui Paulo 			    os_memcmp_const(pos->pmkid, entry->pmkid,
1905b9c547cSRui Paulo 					    PMKID_LEN) == 0) {
19139beb93cSSam Leffler 				wpa_printf(MSG_DEBUG, "WPA: reusing previous "
19239beb93cSSam Leffler 					   "PMKSA entry");
19339beb93cSSam Leffler 				os_free(entry);
19439beb93cSSam Leffler 				return pos;
19539beb93cSSam Leffler 			}
19639beb93cSSam Leffler 			if (prev == NULL)
19739beb93cSSam Leffler 				pmksa->pmksa = pos->next;
19839beb93cSSam Leffler 			else
19939beb93cSSam Leffler 				prev->next = pos->next;
200f05cddf9SRui Paulo 
201f05cddf9SRui Paulo 			/*
202f05cddf9SRui Paulo 			 * If OKC is used, there may be other PMKSA cache
203f05cddf9SRui Paulo 			 * entries based on the same PMK. These needs to be
204f05cddf9SRui Paulo 			 * flushed so that a new entry can be created based on
2055b9c547cSRui Paulo 			 * the new PMK. Only clear other entries if they have a
2065b9c547cSRui Paulo 			 * matching PMK and this PMK has been used successfully
2075b9c547cSRui Paulo 			 * with the current AP, i.e., if opportunistic flag has
2085b9c547cSRui Paulo 			 * been cleared in wpa_supplicant_key_neg_complete().
209f05cddf9SRui Paulo 			 */
2105b9c547cSRui Paulo 			wpa_printf(MSG_DEBUG, "RSN: Replace PMKSA entry for "
2115b9c547cSRui Paulo 				   "the current AP and any PMKSA cache entry "
2125b9c547cSRui Paulo 				   "that was based on the old PMK");
2135b9c547cSRui Paulo 			if (!pos->opportunistic)
214*85732ac8SCy Schubert 				pmksa_cache_flush(pmksa, entry->network_ctx,
215*85732ac8SCy Schubert 						  pos->pmk, pos->pmk_len);
2165b9c547cSRui Paulo 			pmksa_cache_free_entry(pmksa, pos, PMKSA_REPLACE);
21739beb93cSSam Leffler 			break;
21839beb93cSSam Leffler 		}
21939beb93cSSam Leffler 		prev = pos;
22039beb93cSSam Leffler 		pos = pos->next;
22139beb93cSSam Leffler 	}
22239beb93cSSam Leffler 
22339beb93cSSam Leffler 	if (pmksa->pmksa_count >= pmksa_cache_max_entries && pmksa->pmksa) {
22439beb93cSSam Leffler 		/* Remove the oldest entry to make room for the new entry */
22539beb93cSSam Leffler 		pos = pmksa->pmksa;
226f05cddf9SRui Paulo 
227f05cddf9SRui Paulo 		if (pos == pmksa->sm->cur_pmksa) {
228f05cddf9SRui Paulo 			/*
229f05cddf9SRui Paulo 			 * Never remove the current PMKSA cache entry, since
230f05cddf9SRui Paulo 			 * it's in use, and removing it triggers a needless
231f05cddf9SRui Paulo 			 * deauthentication.
232f05cddf9SRui Paulo 			 */
233f05cddf9SRui Paulo 			pos = pos->next;
234f05cddf9SRui Paulo 			pmksa->pmksa->next = pos ? pos->next : NULL;
235f05cddf9SRui Paulo 		} else
23639beb93cSSam Leffler 			pmksa->pmksa = pos->next;
237f05cddf9SRui Paulo 
238f05cddf9SRui Paulo 		if (pos) {
239f05cddf9SRui Paulo 			wpa_printf(MSG_DEBUG, "RSN: removed the oldest idle "
240f05cddf9SRui Paulo 				   "PMKSA cache entry (for " MACSTR ") to "
241f05cddf9SRui Paulo 				   "make room for new one",
24239beb93cSSam Leffler 				   MAC2STR(pos->aa));
243f05cddf9SRui Paulo 			pmksa_cache_free_entry(pmksa, pos, PMKSA_FREE);
244f05cddf9SRui Paulo 		}
24539beb93cSSam Leffler 	}
24639beb93cSSam Leffler 
24739beb93cSSam Leffler 	/* Add the new entry; order by expiration time */
24839beb93cSSam Leffler 	pos = pmksa->pmksa;
24939beb93cSSam Leffler 	prev = NULL;
25039beb93cSSam Leffler 	while (pos) {
25139beb93cSSam Leffler 		if (pos->expiration > entry->expiration)
25239beb93cSSam Leffler 			break;
25339beb93cSSam Leffler 		prev = pos;
25439beb93cSSam Leffler 		pos = pos->next;
25539beb93cSSam Leffler 	}
25639beb93cSSam Leffler 	if (prev == NULL) {
25739beb93cSSam Leffler 		entry->next = pmksa->pmksa;
25839beb93cSSam Leffler 		pmksa->pmksa = entry;
25939beb93cSSam Leffler 		pmksa_cache_set_expiration(pmksa);
26039beb93cSSam Leffler 	} else {
26139beb93cSSam Leffler 		entry->next = prev->next;
26239beb93cSSam Leffler 		prev->next = entry;
26339beb93cSSam Leffler 	}
26439beb93cSSam Leffler 	pmksa->pmksa_count++;
265f05cddf9SRui Paulo 	wpa_printf(MSG_DEBUG, "RSN: Added PMKSA cache entry for " MACSTR
266*85732ac8SCy Schubert 		   " network_ctx=%p", MAC2STR(entry->aa), entry->network_ctx);
267*85732ac8SCy Schubert 	wpa_sm_add_pmkid(pmksa->sm, entry->network_ctx, entry->aa, entry->pmkid,
268*85732ac8SCy Schubert 			 entry->fils_cache_id_set ? entry->fils_cache_id : NULL,
269*85732ac8SCy Schubert 			 entry->pmk, entry->pmk_len);
27039beb93cSSam Leffler 
27139beb93cSSam Leffler 	return entry;
27239beb93cSSam Leffler }
27339beb93cSSam Leffler 
27439beb93cSSam Leffler 
27539beb93cSSam Leffler /**
276f05cddf9SRui Paulo  * pmksa_cache_flush - Flush PMKSA cache entries for a specific network
277f05cddf9SRui Paulo  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init()
278f05cddf9SRui Paulo  * @network_ctx: Network configuration context or %NULL to flush all entries
2795b9c547cSRui Paulo  * @pmk: PMK to match for or %NYLL to match all PMKs
2805b9c547cSRui Paulo  * @pmk_len: PMK length
281f05cddf9SRui Paulo  */
2825b9c547cSRui Paulo void pmksa_cache_flush(struct rsn_pmksa_cache *pmksa, void *network_ctx,
2835b9c547cSRui Paulo 		       const u8 *pmk, size_t pmk_len)
284f05cddf9SRui Paulo {
285f05cddf9SRui Paulo 	struct rsn_pmksa_cache_entry *entry, *prev = NULL, *tmp;
286f05cddf9SRui Paulo 	int removed = 0;
287f05cddf9SRui Paulo 
288f05cddf9SRui Paulo 	entry = pmksa->pmksa;
289f05cddf9SRui Paulo 	while (entry) {
2905b9c547cSRui Paulo 		if ((entry->network_ctx == network_ctx ||
2915b9c547cSRui Paulo 		     network_ctx == NULL) &&
2925b9c547cSRui Paulo 		    (pmk == NULL ||
2935b9c547cSRui Paulo 		     (pmk_len == entry->pmk_len &&
2945b9c547cSRui Paulo 		      os_memcmp(pmk, entry->pmk, pmk_len) == 0))) {
295f05cddf9SRui Paulo 			wpa_printf(MSG_DEBUG, "RSN: Flush PMKSA cache entry "
296f05cddf9SRui Paulo 				   "for " MACSTR, MAC2STR(entry->aa));
297f05cddf9SRui Paulo 			if (prev)
298f05cddf9SRui Paulo 				prev->next = entry->next;
299f05cddf9SRui Paulo 			else
300f05cddf9SRui Paulo 				pmksa->pmksa = entry->next;
301f05cddf9SRui Paulo 			tmp = entry;
302f05cddf9SRui Paulo 			entry = entry->next;
303f05cddf9SRui Paulo 			pmksa_cache_free_entry(pmksa, tmp, PMKSA_FREE);
304f05cddf9SRui Paulo 			removed++;
305f05cddf9SRui Paulo 		} else {
306f05cddf9SRui Paulo 			prev = entry;
307f05cddf9SRui Paulo 			entry = entry->next;
308f05cddf9SRui Paulo 		}
309f05cddf9SRui Paulo 	}
310f05cddf9SRui Paulo 	if (removed)
311f05cddf9SRui Paulo 		pmksa_cache_set_expiration(pmksa);
312f05cddf9SRui Paulo }
313f05cddf9SRui Paulo 
314f05cddf9SRui Paulo 
315f05cddf9SRui Paulo /**
31639beb93cSSam Leffler  * pmksa_cache_deinit - Free all entries in PMKSA cache
31739beb93cSSam Leffler  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init()
31839beb93cSSam Leffler  */
31939beb93cSSam Leffler void pmksa_cache_deinit(struct rsn_pmksa_cache *pmksa)
32039beb93cSSam Leffler {
32139beb93cSSam Leffler 	struct rsn_pmksa_cache_entry *entry, *prev;
32239beb93cSSam Leffler 
32339beb93cSSam Leffler 	if (pmksa == NULL)
32439beb93cSSam Leffler 		return;
32539beb93cSSam Leffler 
32639beb93cSSam Leffler 	entry = pmksa->pmksa;
32739beb93cSSam Leffler 	pmksa->pmksa = NULL;
32839beb93cSSam Leffler 	while (entry) {
32939beb93cSSam Leffler 		prev = entry;
33039beb93cSSam Leffler 		entry = entry->next;
33139beb93cSSam Leffler 		os_free(prev);
33239beb93cSSam Leffler 	}
33339beb93cSSam Leffler 	pmksa_cache_set_expiration(pmksa);
33439beb93cSSam Leffler 	os_free(pmksa);
33539beb93cSSam Leffler }
33639beb93cSSam Leffler 
33739beb93cSSam Leffler 
33839beb93cSSam Leffler /**
33939beb93cSSam Leffler  * pmksa_cache_get - Fetch a PMKSA cache entry
34039beb93cSSam Leffler  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init()
34139beb93cSSam Leffler  * @aa: Authenticator address or %NULL to match any
34239beb93cSSam Leffler  * @pmkid: PMKID or %NULL to match any
343f05cddf9SRui Paulo  * @network_ctx: Network context or %NULL to match any
344*85732ac8SCy Schubert  * @akmp: Specific AKMP to search for or 0 for any
34539beb93cSSam Leffler  * Returns: Pointer to PMKSA cache entry or %NULL if no match was found
34639beb93cSSam Leffler  */
34739beb93cSSam Leffler struct rsn_pmksa_cache_entry * pmksa_cache_get(struct rsn_pmksa_cache *pmksa,
348f05cddf9SRui Paulo 					       const u8 *aa, const u8 *pmkid,
349*85732ac8SCy Schubert 					       const void *network_ctx,
350*85732ac8SCy Schubert 					       int akmp)
35139beb93cSSam Leffler {
35239beb93cSSam Leffler 	struct rsn_pmksa_cache_entry *entry = pmksa->pmksa;
35339beb93cSSam Leffler 	while (entry) {
35439beb93cSSam Leffler 		if ((aa == NULL || os_memcmp(entry->aa, aa, ETH_ALEN) == 0) &&
35539beb93cSSam Leffler 		    (pmkid == NULL ||
356f05cddf9SRui Paulo 		     os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0) &&
357*85732ac8SCy Schubert 		    (!akmp || akmp == entry->akmp) &&
358f05cddf9SRui Paulo 		    (network_ctx == NULL || network_ctx == entry->network_ctx))
35939beb93cSSam Leffler 			return entry;
36039beb93cSSam Leffler 		entry = entry->next;
36139beb93cSSam Leffler 	}
36239beb93cSSam Leffler 	return NULL;
36339beb93cSSam Leffler }
36439beb93cSSam Leffler 
36539beb93cSSam Leffler 
36639beb93cSSam Leffler static struct rsn_pmksa_cache_entry *
36739beb93cSSam Leffler pmksa_cache_clone_entry(struct rsn_pmksa_cache *pmksa,
36839beb93cSSam Leffler 			const struct rsn_pmksa_cache_entry *old_entry,
36939beb93cSSam Leffler 			const u8 *aa)
37039beb93cSSam Leffler {
37139beb93cSSam Leffler 	struct rsn_pmksa_cache_entry *new_entry;
372*85732ac8SCy Schubert 	os_time_t old_expiration = old_entry->expiration;
37339beb93cSSam Leffler 
37439beb93cSSam Leffler 	new_entry = pmksa_cache_add(pmksa, old_entry->pmk, old_entry->pmk_len,
375780fb4a2SCy Schubert 				    NULL, NULL, 0,
37639beb93cSSam Leffler 				    aa, pmksa->sm->own_addr,
377*85732ac8SCy Schubert 				    old_entry->network_ctx, old_entry->akmp,
378*85732ac8SCy Schubert 				    old_entry->fils_cache_id_set ?
379*85732ac8SCy Schubert 				    old_entry->fils_cache_id : NULL);
38039beb93cSSam Leffler 	if (new_entry == NULL)
38139beb93cSSam Leffler 		return NULL;
38239beb93cSSam Leffler 
38339beb93cSSam Leffler 	/* TODO: reorder entries based on expiration time? */
384*85732ac8SCy Schubert 	new_entry->expiration = old_expiration;
38539beb93cSSam Leffler 	new_entry->opportunistic = 1;
38639beb93cSSam Leffler 
38739beb93cSSam Leffler 	return new_entry;
38839beb93cSSam Leffler }
38939beb93cSSam Leffler 
39039beb93cSSam Leffler 
39139beb93cSSam Leffler /**
39239beb93cSSam Leffler  * pmksa_cache_get_opportunistic - Try to get an opportunistic PMKSA entry
39339beb93cSSam Leffler  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init()
39439beb93cSSam Leffler  * @network_ctx: Network configuration context
39539beb93cSSam Leffler  * @aa: Authenticator address for the new AP
396*85732ac8SCy Schubert  * @akmp: Specific AKMP to search for or 0 for any
39739beb93cSSam Leffler  * Returns: Pointer to a new PMKSA cache entry or %NULL if not available
39839beb93cSSam Leffler  *
39939beb93cSSam Leffler  * Try to create a new PMKSA cache entry opportunistically by guessing that the
40039beb93cSSam Leffler  * new AP is sharing the same PMK as another AP that has the same SSID and has
40139beb93cSSam Leffler  * already an entry in PMKSA cache.
40239beb93cSSam Leffler  */
40339beb93cSSam Leffler struct rsn_pmksa_cache_entry *
40439beb93cSSam Leffler pmksa_cache_get_opportunistic(struct rsn_pmksa_cache *pmksa, void *network_ctx,
405*85732ac8SCy Schubert 			      const u8 *aa, int akmp)
40639beb93cSSam Leffler {
40739beb93cSSam Leffler 	struct rsn_pmksa_cache_entry *entry = pmksa->pmksa;
40839beb93cSSam Leffler 
409f05cddf9SRui Paulo 	wpa_printf(MSG_DEBUG, "RSN: Consider " MACSTR " for OKC", MAC2STR(aa));
41039beb93cSSam Leffler 	if (network_ctx == NULL)
41139beb93cSSam Leffler 		return NULL;
41239beb93cSSam Leffler 	while (entry) {
413*85732ac8SCy Schubert 		if (entry->network_ctx == network_ctx &&
414*85732ac8SCy Schubert 		    (!akmp || entry->akmp == akmp)) {
41539beb93cSSam Leffler 			entry = pmksa_cache_clone_entry(pmksa, entry, aa);
41639beb93cSSam Leffler 			if (entry) {
41739beb93cSSam Leffler 				wpa_printf(MSG_DEBUG, "RSN: added "
41839beb93cSSam Leffler 					   "opportunistic PMKSA cache entry "
41939beb93cSSam Leffler 					   "for " MACSTR, MAC2STR(aa));
42039beb93cSSam Leffler 			}
42139beb93cSSam Leffler 			return entry;
42239beb93cSSam Leffler 		}
42339beb93cSSam Leffler 		entry = entry->next;
42439beb93cSSam Leffler 	}
42539beb93cSSam Leffler 	return NULL;
42639beb93cSSam Leffler }
42739beb93cSSam Leffler 
42839beb93cSSam Leffler 
429*85732ac8SCy Schubert static struct rsn_pmksa_cache_entry *
430*85732ac8SCy Schubert pmksa_cache_get_fils_cache_id(struct rsn_pmksa_cache *pmksa,
431*85732ac8SCy Schubert 			      const void *network_ctx, const u8 *cache_id)
432*85732ac8SCy Schubert {
433*85732ac8SCy Schubert 	struct rsn_pmksa_cache_entry *entry;
434*85732ac8SCy Schubert 
435*85732ac8SCy Schubert 	for (entry = pmksa->pmksa; entry; entry = entry->next) {
436*85732ac8SCy Schubert 		if (network_ctx == entry->network_ctx &&
437*85732ac8SCy Schubert 		    entry->fils_cache_id_set &&
438*85732ac8SCy Schubert 		    os_memcmp(cache_id, entry->fils_cache_id,
439*85732ac8SCy Schubert 			      FILS_CACHE_ID_LEN) == 0)
440*85732ac8SCy Schubert 			return entry;
441*85732ac8SCy Schubert 	}
442*85732ac8SCy Schubert 
443*85732ac8SCy Schubert 	return NULL;
444*85732ac8SCy Schubert }
445*85732ac8SCy Schubert 
446*85732ac8SCy Schubert 
44739beb93cSSam Leffler /**
44839beb93cSSam Leffler  * pmksa_cache_get_current - Get the current used PMKSA entry
44939beb93cSSam Leffler  * @sm: Pointer to WPA state machine data from wpa_sm_init()
45039beb93cSSam Leffler  * Returns: Pointer to the current PMKSA cache entry or %NULL if not available
45139beb93cSSam Leffler  */
45239beb93cSSam Leffler struct rsn_pmksa_cache_entry * pmksa_cache_get_current(struct wpa_sm *sm)
45339beb93cSSam Leffler {
45439beb93cSSam Leffler 	if (sm == NULL)
45539beb93cSSam Leffler 		return NULL;
45639beb93cSSam Leffler 	return sm->cur_pmksa;
45739beb93cSSam Leffler }
45839beb93cSSam Leffler 
45939beb93cSSam Leffler 
46039beb93cSSam Leffler /**
46139beb93cSSam Leffler  * pmksa_cache_clear_current - Clear the current PMKSA entry selection
46239beb93cSSam Leffler  * @sm: Pointer to WPA state machine data from wpa_sm_init()
46339beb93cSSam Leffler  */
46439beb93cSSam Leffler void pmksa_cache_clear_current(struct wpa_sm *sm)
46539beb93cSSam Leffler {
46639beb93cSSam Leffler 	if (sm == NULL)
46739beb93cSSam Leffler 		return;
46839beb93cSSam Leffler 	sm->cur_pmksa = NULL;
46939beb93cSSam Leffler }
47039beb93cSSam Leffler 
47139beb93cSSam Leffler 
47239beb93cSSam Leffler /**
47339beb93cSSam Leffler  * pmksa_cache_set_current - Set the current PMKSA entry selection
47439beb93cSSam Leffler  * @sm: Pointer to WPA state machine data from wpa_sm_init()
47539beb93cSSam Leffler  * @pmkid: PMKID for selecting PMKSA or %NULL if not used
47639beb93cSSam Leffler  * @bssid: BSSID for PMKSA or %NULL if not used
47739beb93cSSam Leffler  * @network_ctx: Network configuration context
47839beb93cSSam Leffler  * @try_opportunistic: Whether to allow opportunistic PMKSA caching
479*85732ac8SCy Schubert  * @fils_cache_id: Pointer to FILS Cache Identifier or %NULL if not used
48039beb93cSSam Leffler  * Returns: 0 if PMKSA was found or -1 if no matching entry was found
48139beb93cSSam Leffler  */
48239beb93cSSam Leffler int pmksa_cache_set_current(struct wpa_sm *sm, const u8 *pmkid,
48339beb93cSSam Leffler 			    const u8 *bssid, void *network_ctx,
484*85732ac8SCy Schubert 			    int try_opportunistic, const u8 *fils_cache_id,
485*85732ac8SCy Schubert 			    int akmp)
48639beb93cSSam Leffler {
48739beb93cSSam Leffler 	struct rsn_pmksa_cache *pmksa = sm->pmksa;
488f05cddf9SRui Paulo 	wpa_printf(MSG_DEBUG, "RSN: PMKSA cache search - network_ctx=%p "
489*85732ac8SCy Schubert 		   "try_opportunistic=%d akmp=0x%x",
490*85732ac8SCy Schubert 		   network_ctx, try_opportunistic, akmp);
491f05cddf9SRui Paulo 	if (pmkid)
492f05cddf9SRui Paulo 		wpa_hexdump(MSG_DEBUG, "RSN: Search for PMKID",
493f05cddf9SRui Paulo 			    pmkid, PMKID_LEN);
494f05cddf9SRui Paulo 	if (bssid)
495f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "RSN: Search for BSSID " MACSTR,
496f05cddf9SRui Paulo 			   MAC2STR(bssid));
497*85732ac8SCy Schubert 	if (fils_cache_id)
498*85732ac8SCy Schubert 		wpa_printf(MSG_DEBUG,
499*85732ac8SCy Schubert 			   "RSN: Search for FILS Cache Identifier %02x%02x",
500*85732ac8SCy Schubert 			   fils_cache_id[0], fils_cache_id[1]);
501f05cddf9SRui Paulo 
50239beb93cSSam Leffler 	sm->cur_pmksa = NULL;
50339beb93cSSam Leffler 	if (pmkid)
504f05cddf9SRui Paulo 		sm->cur_pmksa = pmksa_cache_get(pmksa, NULL, pmkid,
505*85732ac8SCy Schubert 						network_ctx, akmp);
50639beb93cSSam Leffler 	if (sm->cur_pmksa == NULL && bssid)
507f05cddf9SRui Paulo 		sm->cur_pmksa = pmksa_cache_get(pmksa, bssid, NULL,
508*85732ac8SCy Schubert 						network_ctx, akmp);
50939beb93cSSam Leffler 	if (sm->cur_pmksa == NULL && try_opportunistic && bssid)
51039beb93cSSam Leffler 		sm->cur_pmksa = pmksa_cache_get_opportunistic(pmksa,
51139beb93cSSam Leffler 							      network_ctx,
512*85732ac8SCy Schubert 							      bssid, akmp);
513*85732ac8SCy Schubert 	if (sm->cur_pmksa == NULL && fils_cache_id)
514*85732ac8SCy Schubert 		sm->cur_pmksa = pmksa_cache_get_fils_cache_id(pmksa,
515*85732ac8SCy Schubert 							      network_ctx,
516*85732ac8SCy Schubert 							      fils_cache_id);
51739beb93cSSam Leffler 	if (sm->cur_pmksa) {
518f05cddf9SRui Paulo 		wpa_hexdump(MSG_DEBUG, "RSN: PMKSA cache entry found - PMKID",
51939beb93cSSam Leffler 			    sm->cur_pmksa->pmkid, PMKID_LEN);
52039beb93cSSam Leffler 		return 0;
52139beb93cSSam Leffler 	}
522f05cddf9SRui Paulo 	wpa_printf(MSG_DEBUG, "RSN: No PMKSA cache entry found");
52339beb93cSSam Leffler 	return -1;
52439beb93cSSam Leffler }
52539beb93cSSam Leffler 
52639beb93cSSam Leffler 
52739beb93cSSam Leffler /**
52839beb93cSSam Leffler  * pmksa_cache_list - Dump text list of entries in PMKSA cache
529e28a4053SRui Paulo  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init()
53039beb93cSSam Leffler  * @buf: Buffer for the list
53139beb93cSSam Leffler  * @len: Length of the buffer
53239beb93cSSam Leffler  * Returns: number of bytes written to buffer
53339beb93cSSam Leffler  *
53439beb93cSSam Leffler  * This function is used to generate a text format representation of the
53539beb93cSSam Leffler  * current PMKSA cache contents for the ctrl_iface PMKSA command.
53639beb93cSSam Leffler  */
537e28a4053SRui Paulo int pmksa_cache_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len)
53839beb93cSSam Leffler {
53939beb93cSSam Leffler 	int i, ret;
54039beb93cSSam Leffler 	char *pos = buf;
54139beb93cSSam Leffler 	struct rsn_pmksa_cache_entry *entry;
5425b9c547cSRui Paulo 	struct os_reltime now;
543*85732ac8SCy Schubert 	int cache_id_used = 0;
544*85732ac8SCy Schubert 
545*85732ac8SCy Schubert 	for (entry = pmksa->pmksa; entry; entry = entry->next) {
546*85732ac8SCy Schubert 		if (entry->fils_cache_id_set) {
547*85732ac8SCy Schubert 			cache_id_used = 1;
548*85732ac8SCy Schubert 			break;
549*85732ac8SCy Schubert 		}
550*85732ac8SCy Schubert 	}
55139beb93cSSam Leffler 
5525b9c547cSRui Paulo 	os_get_reltime(&now);
55339beb93cSSam Leffler 	ret = os_snprintf(pos, buf + len - pos,
55439beb93cSSam Leffler 			  "Index / AA / PMKID / expiration (in seconds) / "
555*85732ac8SCy Schubert 			  "opportunistic%s\n",
556*85732ac8SCy Schubert 			  cache_id_used ? " / FILS Cache Identifier" : "");
5575b9c547cSRui Paulo 	if (os_snprintf_error(buf + len - pos, ret))
55839beb93cSSam Leffler 		return pos - buf;
55939beb93cSSam Leffler 	pos += ret;
56039beb93cSSam Leffler 	i = 0;
561e28a4053SRui Paulo 	entry = pmksa->pmksa;
56239beb93cSSam Leffler 	while (entry) {
56339beb93cSSam Leffler 		i++;
56439beb93cSSam Leffler 		ret = os_snprintf(pos, buf + len - pos, "%d " MACSTR " ",
56539beb93cSSam Leffler 				  i, MAC2STR(entry->aa));
5665b9c547cSRui Paulo 		if (os_snprintf_error(buf + len - pos, ret))
56739beb93cSSam Leffler 			return pos - buf;
56839beb93cSSam Leffler 		pos += ret;
56939beb93cSSam Leffler 		pos += wpa_snprintf_hex(pos, buf + len - pos, entry->pmkid,
57039beb93cSSam Leffler 					PMKID_LEN);
571*85732ac8SCy Schubert 		ret = os_snprintf(pos, buf + len - pos, " %d %d",
57239beb93cSSam Leffler 				  (int) (entry->expiration - now.sec),
57339beb93cSSam Leffler 				  entry->opportunistic);
5745b9c547cSRui Paulo 		if (os_snprintf_error(buf + len - pos, ret))
57539beb93cSSam Leffler 			return pos - buf;
57639beb93cSSam Leffler 		pos += ret;
577*85732ac8SCy Schubert 		if (entry->fils_cache_id_set) {
578*85732ac8SCy Schubert 			ret = os_snprintf(pos, buf + len - pos, " %02x%02x",
579*85732ac8SCy Schubert 					  entry->fils_cache_id[0],
580*85732ac8SCy Schubert 					  entry->fils_cache_id[1]);
581*85732ac8SCy Schubert 			if (os_snprintf_error(buf + len - pos, ret))
582*85732ac8SCy Schubert 				return pos - buf;
583*85732ac8SCy Schubert 			pos += ret;
584*85732ac8SCy Schubert 		}
585*85732ac8SCy Schubert 		ret = os_snprintf(pos, buf + len - pos, "\n");
586*85732ac8SCy Schubert 		if (os_snprintf_error(buf + len - pos, ret))
587*85732ac8SCy Schubert 			return pos - buf;
588*85732ac8SCy Schubert 		pos += ret;
58939beb93cSSam Leffler 		entry = entry->next;
59039beb93cSSam Leffler 	}
59139beb93cSSam Leffler 	return pos - buf;
59239beb93cSSam Leffler }
59339beb93cSSam Leffler 
59439beb93cSSam Leffler 
595*85732ac8SCy Schubert struct rsn_pmksa_cache_entry * pmksa_cache_head(struct rsn_pmksa_cache *pmksa)
596*85732ac8SCy Schubert {
597*85732ac8SCy Schubert 	return pmksa->pmksa;
598*85732ac8SCy Schubert }
599*85732ac8SCy Schubert 
600*85732ac8SCy Schubert 
60139beb93cSSam Leffler /**
60239beb93cSSam Leffler  * pmksa_cache_init - Initialize PMKSA cache
60339beb93cSSam Leffler  * @free_cb: Callback function to be called when a PMKSA cache entry is freed
60439beb93cSSam Leffler  * @ctx: Context pointer for free_cb function
60539beb93cSSam Leffler  * @sm: Pointer to WPA state machine data from wpa_sm_init()
60639beb93cSSam Leffler  * Returns: Pointer to PMKSA cache data or %NULL on failure
60739beb93cSSam Leffler  */
60839beb93cSSam Leffler struct rsn_pmksa_cache *
60939beb93cSSam Leffler pmksa_cache_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry,
610f05cddf9SRui Paulo 				 void *ctx, enum pmksa_free_reason reason),
61139beb93cSSam Leffler 		 void *ctx, struct wpa_sm *sm)
61239beb93cSSam Leffler {
61339beb93cSSam Leffler 	struct rsn_pmksa_cache *pmksa;
61439beb93cSSam Leffler 
61539beb93cSSam Leffler 	pmksa = os_zalloc(sizeof(*pmksa));
61639beb93cSSam Leffler 	if (pmksa) {
61739beb93cSSam Leffler 		pmksa->free_cb = free_cb;
61839beb93cSSam Leffler 		pmksa->ctx = ctx;
61939beb93cSSam Leffler 		pmksa->sm = sm;
62039beb93cSSam Leffler 	}
62139beb93cSSam Leffler 
62239beb93cSSam Leffler 	return pmksa;
62339beb93cSSam Leffler }
62439beb93cSSam Leffler 
6255b9c547cSRui Paulo #endif /* IEEE8021X_EAPOL */
626