1214501Srpaulo/*
2214501Srpaulo * hostapd - PMKSA cache for IEEE 802.11i RSN
3281806Srpaulo * Copyright (c) 2004-2008, 2012-2015, Jouni Malinen <j@w1.fi>
4214501Srpaulo *
5252726Srpaulo * This software may be distributed under the terms of the BSD license.
6252726Srpaulo * See README for more details.
7214501Srpaulo */
8214501Srpaulo
9214501Srpaulo#include "utils/includes.h"
10214501Srpaulo
11214501Srpaulo#include "utils/common.h"
12214501Srpaulo#include "utils/eloop.h"
13214501Srpaulo#include "eapol_auth/eapol_auth_sm.h"
14214501Srpaulo#include "eapol_auth/eapol_auth_sm_i.h"
15281806Srpaulo#include "radius/radius_das.h"
16214501Srpaulo#include "sta_info.h"
17214501Srpaulo#include "ap_config.h"
18214501Srpaulo#include "pmksa_cache_auth.h"
19214501Srpaulo
20214501Srpaulo
21214501Srpaulostatic const int pmksa_cache_max_entries = 1024;
22214501Srpaulostatic const int dot11RSNAConfigPMKLifetime = 43200;
23214501Srpaulo
24214501Srpaulostruct rsn_pmksa_cache {
25214501Srpaulo#define PMKID_HASH_SIZE 128
26214501Srpaulo#define PMKID_HASH(pmkid) (unsigned int) ((pmkid)[0] & 0x7f)
27214501Srpaulo	struct rsn_pmksa_cache_entry *pmkid[PMKID_HASH_SIZE];
28214501Srpaulo	struct rsn_pmksa_cache_entry *pmksa;
29214501Srpaulo	int pmksa_count;
30214501Srpaulo
31214501Srpaulo	void (*free_cb)(struct rsn_pmksa_cache_entry *entry, void *ctx);
32214501Srpaulo	void *ctx;
33214501Srpaulo};
34214501Srpaulo
35214501Srpaulo
36214501Srpaulostatic void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa);
37214501Srpaulo
38214501Srpaulo
39214501Srpaulostatic void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry)
40214501Srpaulo{
41337817Scy	os_free(entry->vlan_desc);
42214501Srpaulo	os_free(entry->identity);
43252726Srpaulo	wpabuf_free(entry->cui);
44214501Srpaulo#ifndef CONFIG_NO_RADIUS
45214501Srpaulo	radius_free_class(&entry->radius_class);
46214501Srpaulo#endif /* CONFIG_NO_RADIUS */
47281806Srpaulo	bin_clear_free(entry, sizeof(*entry));
48214501Srpaulo}
49214501Srpaulo
50214501Srpaulo
51281806Srpaulovoid pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa,
52281806Srpaulo			    struct rsn_pmksa_cache_entry *entry)
53214501Srpaulo{
54214501Srpaulo	struct rsn_pmksa_cache_entry *pos, *prev;
55281806Srpaulo	unsigned int hash;
56214501Srpaulo
57214501Srpaulo	pmksa->pmksa_count--;
58214501Srpaulo	pmksa->free_cb(entry, pmksa->ctx);
59281806Srpaulo
60281806Srpaulo	/* unlink from hash list */
61281806Srpaulo	hash = PMKID_HASH(entry->pmkid);
62281806Srpaulo	pos = pmksa->pmkid[hash];
63214501Srpaulo	prev = NULL;
64214501Srpaulo	while (pos) {
65214501Srpaulo		if (pos == entry) {
66281806Srpaulo			if (prev != NULL)
67281806Srpaulo				prev->hnext = entry->hnext;
68281806Srpaulo			else
69281806Srpaulo				pmksa->pmkid[hash] = entry->hnext;
70214501Srpaulo			break;
71214501Srpaulo		}
72214501Srpaulo		prev = pos;
73214501Srpaulo		pos = pos->hnext;
74214501Srpaulo	}
75214501Srpaulo
76281806Srpaulo	/* unlink from entry list */
77214501Srpaulo	pos = pmksa->pmksa;
78214501Srpaulo	prev = NULL;
79214501Srpaulo	while (pos) {
80214501Srpaulo		if (pos == entry) {
81214501Srpaulo			if (prev != NULL)
82281806Srpaulo				prev->next = entry->next;
83214501Srpaulo			else
84281806Srpaulo				pmksa->pmksa = entry->next;
85214501Srpaulo			break;
86214501Srpaulo		}
87214501Srpaulo		prev = pos;
88214501Srpaulo		pos = pos->next;
89214501Srpaulo	}
90281806Srpaulo
91214501Srpaulo	_pmksa_cache_free_entry(entry);
92214501Srpaulo}
93214501Srpaulo
94214501Srpaulo
95337817Scy/**
96337817Scy * pmksa_cache_auth_flush - Flush all PMKSA cache entries
97337817Scy * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
98337817Scy */
99337817Scyvoid pmksa_cache_auth_flush(struct rsn_pmksa_cache *pmksa)
100337817Scy{
101337817Scy	while (pmksa->pmksa) {
102337817Scy		wpa_printf(MSG_DEBUG, "RSN: Flush PMKSA cache entry for "
103337817Scy			   MACSTR, MAC2STR(pmksa->pmksa->spa));
104337817Scy		pmksa_cache_free_entry(pmksa, pmksa->pmksa);
105337817Scy	}
106337817Scy}
107337817Scy
108337817Scy
109214501Srpaulostatic void pmksa_cache_expire(void *eloop_ctx, void *timeout_ctx)
110214501Srpaulo{
111214501Srpaulo	struct rsn_pmksa_cache *pmksa = eloop_ctx;
112281806Srpaulo	struct os_reltime now;
113214501Srpaulo
114281806Srpaulo	os_get_reltime(&now);
115214501Srpaulo	while (pmksa->pmksa && pmksa->pmksa->expiration <= now.sec) {
116214501Srpaulo		wpa_printf(MSG_DEBUG, "RSN: expired PMKSA cache entry for "
117252726Srpaulo			   MACSTR, MAC2STR(pmksa->pmksa->spa));
118252726Srpaulo		pmksa_cache_free_entry(pmksa, pmksa->pmksa);
119214501Srpaulo	}
120214501Srpaulo
121214501Srpaulo	pmksa_cache_set_expiration(pmksa);
122214501Srpaulo}
123214501Srpaulo
124214501Srpaulo
125214501Srpaulostatic void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa)
126214501Srpaulo{
127214501Srpaulo	int sec;
128281806Srpaulo	struct os_reltime now;
129214501Srpaulo
130214501Srpaulo	eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL);
131214501Srpaulo	if (pmksa->pmksa == NULL)
132214501Srpaulo		return;
133281806Srpaulo	os_get_reltime(&now);
134214501Srpaulo	sec = pmksa->pmksa->expiration - now.sec;
135214501Srpaulo	if (sec < 0)
136214501Srpaulo		sec = 0;
137214501Srpaulo	eloop_register_timeout(sec + 1, 0, pmksa_cache_expire, pmksa, NULL);
138214501Srpaulo}
139214501Srpaulo
140214501Srpaulo
141214501Srpaulostatic void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache_entry *entry,
142214501Srpaulo					struct eapol_state_machine *eapol)
143214501Srpaulo{
144337817Scy	struct vlan_description *vlan_desc;
145337817Scy
146214501Srpaulo	if (eapol == NULL)
147214501Srpaulo		return;
148214501Srpaulo
149214501Srpaulo	if (eapol->identity) {
150214501Srpaulo		entry->identity = os_malloc(eapol->identity_len);
151214501Srpaulo		if (entry->identity) {
152214501Srpaulo			entry->identity_len = eapol->identity_len;
153214501Srpaulo			os_memcpy(entry->identity, eapol->identity,
154214501Srpaulo				  eapol->identity_len);
155214501Srpaulo		}
156214501Srpaulo	}
157214501Srpaulo
158252726Srpaulo	if (eapol->radius_cui)
159252726Srpaulo		entry->cui = wpabuf_dup(eapol->radius_cui);
160252726Srpaulo
161214501Srpaulo#ifndef CONFIG_NO_RADIUS
162214501Srpaulo	radius_copy_class(&entry->radius_class, &eapol->radius_class);
163214501Srpaulo#endif /* CONFIG_NO_RADIUS */
164214501Srpaulo
165214501Srpaulo	entry->eap_type_authsrv = eapol->eap_type_authsrv;
166281806Srpaulo
167337817Scy	vlan_desc = ((struct sta_info *) eapol->sta)->vlan_desc;
168337817Scy	if (vlan_desc && vlan_desc->notempty) {
169337817Scy		entry->vlan_desc = os_zalloc(sizeof(struct vlan_description));
170337817Scy		if (entry->vlan_desc)
171337817Scy			*entry->vlan_desc = *vlan_desc;
172337817Scy	} else {
173337817Scy		entry->vlan_desc = NULL;
174337817Scy	}
175337817Scy
176337817Scy	entry->acct_multi_session_id = eapol->acct_multi_session_id;
177214501Srpaulo}
178214501Srpaulo
179214501Srpaulo
180337817Scyvoid pmksa_cache_to_eapol_data(struct hostapd_data *hapd,
181337817Scy			       struct rsn_pmksa_cache_entry *entry,
182214501Srpaulo			       struct eapol_state_machine *eapol)
183214501Srpaulo{
184214501Srpaulo	if (entry == NULL || eapol == NULL)
185214501Srpaulo		return;
186214501Srpaulo
187214501Srpaulo	if (entry->identity) {
188214501Srpaulo		os_free(eapol->identity);
189214501Srpaulo		eapol->identity = os_malloc(entry->identity_len);
190214501Srpaulo		if (eapol->identity) {
191214501Srpaulo			eapol->identity_len = entry->identity_len;
192214501Srpaulo			os_memcpy(eapol->identity, entry->identity,
193214501Srpaulo				  entry->identity_len);
194214501Srpaulo		}
195214501Srpaulo		wpa_hexdump_ascii(MSG_DEBUG, "STA identity from PMKSA",
196214501Srpaulo				  eapol->identity, eapol->identity_len);
197214501Srpaulo	}
198214501Srpaulo
199252726Srpaulo	if (entry->cui) {
200252726Srpaulo		wpabuf_free(eapol->radius_cui);
201252726Srpaulo		eapol->radius_cui = wpabuf_dup(entry->cui);
202252726Srpaulo	}
203252726Srpaulo
204214501Srpaulo#ifndef CONFIG_NO_RADIUS
205214501Srpaulo	radius_free_class(&eapol->radius_class);
206214501Srpaulo	radius_copy_class(&eapol->radius_class, &entry->radius_class);
207214501Srpaulo#endif /* CONFIG_NO_RADIUS */
208214501Srpaulo	if (eapol->radius_class.attr) {
209214501Srpaulo		wpa_printf(MSG_DEBUG, "Copied %lu Class attribute(s) from "
210214501Srpaulo			   "PMKSA", (unsigned long) eapol->radius_class.count);
211214501Srpaulo	}
212214501Srpaulo
213214501Srpaulo	eapol->eap_type_authsrv = entry->eap_type_authsrv;
214337817Scy#ifndef CONFIG_NO_VLAN
215337817Scy	ap_sta_set_vlan(hapd, eapol->sta, entry->vlan_desc);
216337817Scy#endif /* CONFIG_NO_VLAN */
217281806Srpaulo
218337817Scy	eapol->acct_multi_session_id = entry->acct_multi_session_id;
219214501Srpaulo}
220214501Srpaulo
221214501Srpaulo
222214501Srpaulostatic void pmksa_cache_link_entry(struct rsn_pmksa_cache *pmksa,
223214501Srpaulo				   struct rsn_pmksa_cache_entry *entry)
224214501Srpaulo{
225214501Srpaulo	struct rsn_pmksa_cache_entry *pos, *prev;
226281806Srpaulo	int hash;
227214501Srpaulo
228214501Srpaulo	/* Add the new entry; order by expiration time */
229214501Srpaulo	pos = pmksa->pmksa;
230214501Srpaulo	prev = NULL;
231214501Srpaulo	while (pos) {
232214501Srpaulo		if (pos->expiration > entry->expiration)
233214501Srpaulo			break;
234214501Srpaulo		prev = pos;
235214501Srpaulo		pos = pos->next;
236214501Srpaulo	}
237214501Srpaulo	if (prev == NULL) {
238214501Srpaulo		entry->next = pmksa->pmksa;
239214501Srpaulo		pmksa->pmksa = entry;
240214501Srpaulo	} else {
241214501Srpaulo		entry->next = prev->next;
242214501Srpaulo		prev->next = entry;
243214501Srpaulo	}
244214501Srpaulo
245281806Srpaulo	hash = PMKID_HASH(entry->pmkid);
246281806Srpaulo	entry->hnext = pmksa->pmkid[hash];
247281806Srpaulo	pmksa->pmkid[hash] = entry;
248281806Srpaulo
249214501Srpaulo	pmksa->pmksa_count++;
250252726Srpaulo	if (prev == NULL)
251252726Srpaulo		pmksa_cache_set_expiration(pmksa);
252214501Srpaulo	wpa_printf(MSG_DEBUG, "RSN: added PMKSA cache entry for " MACSTR,
253214501Srpaulo		   MAC2STR(entry->spa));
254214501Srpaulo	wpa_hexdump(MSG_DEBUG, "RSN: added PMKID", entry->pmkid, PMKID_LEN);
255214501Srpaulo}
256214501Srpaulo
257214501Srpaulo
258214501Srpaulo/**
259214501Srpaulo * pmksa_cache_auth_add - Add a PMKSA cache entry
260214501Srpaulo * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
261214501Srpaulo * @pmk: The new pairwise master key
262214501Srpaulo * @pmk_len: PMK length in bytes, usually PMK_LEN (32)
263337817Scy * @pmkid: Calculated PMKID
264281806Srpaulo * @kck: Key confirmation key or %NULL if not yet derived
265281806Srpaulo * @kck_len: KCK length in bytes
266214501Srpaulo * @aa: Authenticator address
267214501Srpaulo * @spa: Supplicant address
268214501Srpaulo * @session_timeout: Session timeout
269214501Srpaulo * @eapol: Pointer to EAPOL state machine data
270214501Srpaulo * @akmp: WPA_KEY_MGMT_* used in key derivation
271214501Srpaulo * Returns: Pointer to the added PMKSA cache entry or %NULL on error
272214501Srpaulo *
273214501Srpaulo * This function create a PMKSA entry for a new PMK and adds it to the PMKSA
274214501Srpaulo * cache. If an old entry is already in the cache for the same Supplicant,
275214501Srpaulo * this entry will be replaced with the new entry. PMKID will be calculated
276214501Srpaulo * based on the PMK.
277214501Srpaulo */
278214501Srpaulostruct rsn_pmksa_cache_entry *
279214501Srpaulopmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
280337817Scy		     const u8 *pmk, size_t pmk_len, const u8 *pmkid,
281281806Srpaulo		     const u8 *kck, size_t kck_len,
282281806Srpaulo		     const u8 *aa, const u8 *spa, int session_timeout,
283281806Srpaulo		     struct eapol_state_machine *eapol, int akmp)
284214501Srpaulo{
285346981Scy	struct rsn_pmksa_cache_entry *entry;
286346981Scy
287346981Scy	entry = pmksa_cache_auth_create_entry(pmk, pmk_len, pmkid, kck, kck_len,
288346981Scy					      aa, spa, session_timeout, eapol,
289346981Scy					      akmp);
290346981Scy
291346981Scy	if (pmksa_cache_auth_add_entry(pmksa, entry) < 0)
292346981Scy		return NULL;
293346981Scy
294346981Scy	return entry;
295346981Scy}
296346981Scy
297346981Scy
298346981Scy/**
299346981Scy * pmksa_cache_auth_create_entry - Create a PMKSA cache entry
300346981Scy * @pmk: The new pairwise master key
301346981Scy * @pmk_len: PMK length in bytes, usually PMK_LEN (32)
302346981Scy * @pmkid: Calculated PMKID
303346981Scy * @kck: Key confirmation key or %NULL if not yet derived
304346981Scy * @kck_len: KCK length in bytes
305346981Scy * @aa: Authenticator address
306346981Scy * @spa: Supplicant address
307346981Scy * @session_timeout: Session timeout
308346981Scy * @eapol: Pointer to EAPOL state machine data
309346981Scy * @akmp: WPA_KEY_MGMT_* used in key derivation
310346981Scy * Returns: Pointer to the added PMKSA cache entry or %NULL on error
311346981Scy *
312346981Scy * This function creates a PMKSA entry.
313346981Scy */
314346981Scystruct rsn_pmksa_cache_entry *
315346981Scypmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid,
316346981Scy			      const u8 *kck, size_t kck_len, const u8 *aa,
317346981Scy			      const u8 *spa, int session_timeout,
318346981Scy			      struct eapol_state_machine *eapol, int akmp)
319346981Scy{
320346981Scy	struct rsn_pmksa_cache_entry *entry;
321281806Srpaulo	struct os_reltime now;
322214501Srpaulo
323337817Scy	if (pmk_len > PMK_LEN_MAX)
324214501Srpaulo		return NULL;
325214501Srpaulo
326281806Srpaulo	if (wpa_key_mgmt_suite_b(akmp) && !kck)
327281806Srpaulo		return NULL;
328281806Srpaulo
329214501Srpaulo	entry = os_zalloc(sizeof(*entry));
330214501Srpaulo	if (entry == NULL)
331214501Srpaulo		return NULL;
332214501Srpaulo	os_memcpy(entry->pmk, pmk, pmk_len);
333214501Srpaulo	entry->pmk_len = pmk_len;
334337817Scy	if (pmkid)
335337817Scy		os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
336337817Scy	else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
337281806Srpaulo		rsn_pmkid_suite_b_192(kck, kck_len, aa, spa, entry->pmkid);
338281806Srpaulo	else if (wpa_key_mgmt_suite_b(akmp))
339281806Srpaulo		rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid);
340281806Srpaulo	else
341346981Scy		rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, akmp);
342281806Srpaulo	os_get_reltime(&now);
343214501Srpaulo	entry->expiration = now.sec;
344214501Srpaulo	if (session_timeout > 0)
345214501Srpaulo		entry->expiration += session_timeout;
346214501Srpaulo	else
347214501Srpaulo		entry->expiration += dot11RSNAConfigPMKLifetime;
348214501Srpaulo	entry->akmp = akmp;
349214501Srpaulo	os_memcpy(entry->spa, spa, ETH_ALEN);
350214501Srpaulo	pmksa_cache_from_eapol_data(entry, eapol);
351214501Srpaulo
352346981Scy	return entry;
353346981Scy}
354346981Scy
355346981Scy
356346981Scy/**
357346981Scy * pmksa_cache_auth_add_entry - Add a PMKSA cache entry
358346981Scy * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
359346981Scy * @entry: Pointer to PMKSA cache entry
360346981Scy *
361346981Scy * This function adds PMKSA cache entry to the PMKSA cache. If an old entry is
362346981Scy * already in the cache for the same Supplicant, this entry will be replaced
363346981Scy * with the new entry. PMKID will be calculated based on the PMK.
364346981Scy */
365346981Scyint pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa,
366346981Scy			       struct rsn_pmksa_cache_entry *entry)
367346981Scy{
368346981Scy	struct rsn_pmksa_cache_entry *pos;
369346981Scy
370346981Scy	if (entry == NULL)
371346981Scy		return -1;
372346981Scy
373214501Srpaulo	/* Replace an old entry for the same STA (if found) with the new entry
374214501Srpaulo	 */
375346981Scy	pos = pmksa_cache_auth_get(pmksa, entry->spa, NULL);
376214501Srpaulo	if (pos)
377214501Srpaulo		pmksa_cache_free_entry(pmksa, pos);
378214501Srpaulo
379214501Srpaulo	if (pmksa->pmksa_count >= pmksa_cache_max_entries && pmksa->pmksa) {
380214501Srpaulo		/* Remove the oldest entry to make room for the new entry */
381214501Srpaulo		wpa_printf(MSG_DEBUG, "RSN: removed the oldest PMKSA cache "
382214501Srpaulo			   "entry (for " MACSTR ") to make room for new one",
383214501Srpaulo			   MAC2STR(pmksa->pmksa->spa));
384214501Srpaulo		pmksa_cache_free_entry(pmksa, pmksa->pmksa);
385214501Srpaulo	}
386214501Srpaulo
387214501Srpaulo	pmksa_cache_link_entry(pmksa, entry);
388214501Srpaulo
389346981Scy	return 0;
390214501Srpaulo}
391214501Srpaulo
392214501Srpaulo
393214501Srpaulostruct rsn_pmksa_cache_entry *
394214501Srpaulopmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa,
395214501Srpaulo		    const struct rsn_pmksa_cache_entry *old_entry,
396214501Srpaulo		    const u8 *aa, const u8 *pmkid)
397214501Srpaulo{
398214501Srpaulo	struct rsn_pmksa_cache_entry *entry;
399214501Srpaulo
400214501Srpaulo	entry = os_zalloc(sizeof(*entry));
401214501Srpaulo	if (entry == NULL)
402214501Srpaulo		return NULL;
403214501Srpaulo	os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
404214501Srpaulo	os_memcpy(entry->pmk, old_entry->pmk, old_entry->pmk_len);
405214501Srpaulo	entry->pmk_len = old_entry->pmk_len;
406214501Srpaulo	entry->expiration = old_entry->expiration;
407214501Srpaulo	entry->akmp = old_entry->akmp;
408214501Srpaulo	os_memcpy(entry->spa, old_entry->spa, ETH_ALEN);
409214501Srpaulo	entry->opportunistic = 1;
410214501Srpaulo	if (old_entry->identity) {
411214501Srpaulo		entry->identity = os_malloc(old_entry->identity_len);
412214501Srpaulo		if (entry->identity) {
413214501Srpaulo			entry->identity_len = old_entry->identity_len;
414214501Srpaulo			os_memcpy(entry->identity, old_entry->identity,
415214501Srpaulo				  old_entry->identity_len);
416214501Srpaulo		}
417214501Srpaulo	}
418252726Srpaulo	if (old_entry->cui)
419252726Srpaulo		entry->cui = wpabuf_dup(old_entry->cui);
420214501Srpaulo#ifndef CONFIG_NO_RADIUS
421214501Srpaulo	radius_copy_class(&entry->radius_class, &old_entry->radius_class);
422214501Srpaulo#endif /* CONFIG_NO_RADIUS */
423214501Srpaulo	entry->eap_type_authsrv = old_entry->eap_type_authsrv;
424337817Scy	if (old_entry->vlan_desc) {
425337817Scy		entry->vlan_desc = os_zalloc(sizeof(struct vlan_description));
426337817Scy		if (entry->vlan_desc)
427337817Scy			*entry->vlan_desc = *old_entry->vlan_desc;
428337817Scy	} else {
429337817Scy		entry->vlan_desc = NULL;
430337817Scy	}
431214501Srpaulo	entry->opportunistic = 1;
432214501Srpaulo
433214501Srpaulo	pmksa_cache_link_entry(pmksa, entry);
434214501Srpaulo
435214501Srpaulo	return entry;
436214501Srpaulo}
437214501Srpaulo
438214501Srpaulo
439214501Srpaulo/**
440214501Srpaulo * pmksa_cache_auth_deinit - Free all entries in PMKSA cache
441214501Srpaulo * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
442214501Srpaulo */
443214501Srpaulovoid pmksa_cache_auth_deinit(struct rsn_pmksa_cache *pmksa)
444214501Srpaulo{
445214501Srpaulo	struct rsn_pmksa_cache_entry *entry, *prev;
446214501Srpaulo	int i;
447214501Srpaulo
448214501Srpaulo	if (pmksa == NULL)
449214501Srpaulo		return;
450214501Srpaulo
451214501Srpaulo	entry = pmksa->pmksa;
452214501Srpaulo	while (entry) {
453214501Srpaulo		prev = entry;
454214501Srpaulo		entry = entry->next;
455214501Srpaulo		_pmksa_cache_free_entry(prev);
456214501Srpaulo	}
457214501Srpaulo	eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL);
458281806Srpaulo	pmksa->pmksa_count = 0;
459281806Srpaulo	pmksa->pmksa = NULL;
460214501Srpaulo	for (i = 0; i < PMKID_HASH_SIZE; i++)
461214501Srpaulo		pmksa->pmkid[i] = NULL;
462214501Srpaulo	os_free(pmksa);
463214501Srpaulo}
464214501Srpaulo
465214501Srpaulo
466214501Srpaulo/**
467214501Srpaulo * pmksa_cache_auth_get - Fetch a PMKSA cache entry
468214501Srpaulo * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
469214501Srpaulo * @spa: Supplicant address or %NULL to match any
470214501Srpaulo * @pmkid: PMKID or %NULL to match any
471214501Srpaulo * Returns: Pointer to PMKSA cache entry or %NULL if no match was found
472214501Srpaulo */
473214501Srpaulostruct rsn_pmksa_cache_entry *
474214501Srpaulopmksa_cache_auth_get(struct rsn_pmksa_cache *pmksa,
475214501Srpaulo		     const u8 *spa, const u8 *pmkid)
476214501Srpaulo{
477214501Srpaulo	struct rsn_pmksa_cache_entry *entry;
478214501Srpaulo
479281806Srpaulo	if (pmkid) {
480281806Srpaulo		for (entry = pmksa->pmkid[PMKID_HASH(pmkid)]; entry;
481281806Srpaulo		     entry = entry->hnext) {
482281806Srpaulo			if ((spa == NULL ||
483281806Srpaulo			     os_memcmp(entry->spa, spa, ETH_ALEN) == 0) &&
484281806Srpaulo			    os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0)
485281806Srpaulo				return entry;
486281806Srpaulo		}
487281806Srpaulo	} else {
488281806Srpaulo		for (entry = pmksa->pmksa; entry; entry = entry->next) {
489281806Srpaulo			if (spa == NULL ||
490281806Srpaulo			    os_memcmp(entry->spa, spa, ETH_ALEN) == 0)
491281806Srpaulo				return entry;
492281806Srpaulo		}
493214501Srpaulo	}
494281806Srpaulo
495214501Srpaulo	return NULL;
496214501Srpaulo}
497214501Srpaulo
498214501Srpaulo
499214501Srpaulo/**
500214501Srpaulo * pmksa_cache_get_okc - Fetch a PMKSA cache entry using OKC
501214501Srpaulo * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
502214501Srpaulo * @aa: Authenticator address
503214501Srpaulo * @spa: Supplicant address
504214501Srpaulo * @pmkid: PMKID
505214501Srpaulo * Returns: Pointer to PMKSA cache entry or %NULL if no match was found
506214501Srpaulo *
507214501Srpaulo * Use opportunistic key caching (OKC) to find a PMK for a supplicant.
508214501Srpaulo */
509214501Srpaulostruct rsn_pmksa_cache_entry * pmksa_cache_get_okc(
510214501Srpaulo	struct rsn_pmksa_cache *pmksa, const u8 *aa, const u8 *spa,
511214501Srpaulo	const u8 *pmkid)
512214501Srpaulo{
513214501Srpaulo	struct rsn_pmksa_cache_entry *entry;
514214501Srpaulo	u8 new_pmkid[PMKID_LEN];
515214501Srpaulo
516281806Srpaulo	for (entry = pmksa->pmksa; entry; entry = entry->next) {
517214501Srpaulo		if (os_memcmp(entry->spa, spa, ETH_ALEN) != 0)
518214501Srpaulo			continue;
519214501Srpaulo		rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa, new_pmkid,
520346981Scy			  entry->akmp);
521214501Srpaulo		if (os_memcmp(new_pmkid, pmkid, PMKID_LEN) == 0)
522214501Srpaulo			return entry;
523214501Srpaulo	}
524214501Srpaulo	return NULL;
525214501Srpaulo}
526214501Srpaulo
527214501Srpaulo
528214501Srpaulo/**
529214501Srpaulo * pmksa_cache_auth_init - Initialize PMKSA cache
530214501Srpaulo * @free_cb: Callback function to be called when a PMKSA cache entry is freed
531214501Srpaulo * @ctx: Context pointer for free_cb function
532214501Srpaulo * Returns: Pointer to PMKSA cache data or %NULL on failure
533214501Srpaulo */
534214501Srpaulostruct rsn_pmksa_cache *
535214501Srpaulopmksa_cache_auth_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry,
536214501Srpaulo				      void *ctx), void *ctx)
537214501Srpaulo{
538214501Srpaulo	struct rsn_pmksa_cache *pmksa;
539214501Srpaulo
540214501Srpaulo	pmksa = os_zalloc(sizeof(*pmksa));
541214501Srpaulo	if (pmksa) {
542214501Srpaulo		pmksa->free_cb = free_cb;
543214501Srpaulo		pmksa->ctx = ctx;
544214501Srpaulo	}
545214501Srpaulo
546214501Srpaulo	return pmksa;
547214501Srpaulo}
548281806Srpaulo
549281806Srpaulo
550281806Srpaulostatic int das_attr_match(struct rsn_pmksa_cache_entry *entry,
551281806Srpaulo			  struct radius_das_attrs *attr)
552281806Srpaulo{
553281806Srpaulo	int match = 0;
554281806Srpaulo
555281806Srpaulo	if (attr->sta_addr) {
556281806Srpaulo		if (os_memcmp(attr->sta_addr, entry->spa, ETH_ALEN) != 0)
557281806Srpaulo			return 0;
558281806Srpaulo		match++;
559281806Srpaulo	}
560281806Srpaulo
561281806Srpaulo	if (attr->acct_multi_session_id) {
562281806Srpaulo		char buf[20];
563281806Srpaulo
564337817Scy		if (attr->acct_multi_session_id_len != 16)
565281806Srpaulo			return 0;
566337817Scy		os_snprintf(buf, sizeof(buf), "%016llX",
567337817Scy			    (unsigned long long) entry->acct_multi_session_id);
568337817Scy		if (os_memcmp(attr->acct_multi_session_id, buf, 16) != 0)
569281806Srpaulo			return 0;
570281806Srpaulo		match++;
571281806Srpaulo	}
572281806Srpaulo
573281806Srpaulo	if (attr->cui) {
574281806Srpaulo		if (!entry->cui ||
575281806Srpaulo		    attr->cui_len != wpabuf_len(entry->cui) ||
576281806Srpaulo		    os_memcmp(attr->cui, wpabuf_head(entry->cui),
577281806Srpaulo			      attr->cui_len) != 0)
578281806Srpaulo			return 0;
579281806Srpaulo		match++;
580281806Srpaulo	}
581281806Srpaulo
582281806Srpaulo	if (attr->user_name) {
583281806Srpaulo		if (!entry->identity ||
584281806Srpaulo		    attr->user_name_len != entry->identity_len ||
585281806Srpaulo		    os_memcmp(attr->user_name, entry->identity,
586281806Srpaulo			      attr->user_name_len) != 0)
587281806Srpaulo			return 0;
588281806Srpaulo		match++;
589281806Srpaulo	}
590281806Srpaulo
591281806Srpaulo	return match;
592281806Srpaulo}
593281806Srpaulo
594281806Srpaulo
595281806Srpauloint pmksa_cache_auth_radius_das_disconnect(struct rsn_pmksa_cache *pmksa,
596281806Srpaulo					   struct radius_das_attrs *attr)
597281806Srpaulo{
598281806Srpaulo	int found = 0;
599281806Srpaulo	struct rsn_pmksa_cache_entry *entry, *prev;
600281806Srpaulo
601281806Srpaulo	if (attr->acct_session_id)
602281806Srpaulo		return -1;
603281806Srpaulo
604281806Srpaulo	entry = pmksa->pmksa;
605281806Srpaulo	while (entry) {
606281806Srpaulo		if (das_attr_match(entry, attr)) {
607281806Srpaulo			found++;
608281806Srpaulo			prev = entry;
609281806Srpaulo			entry = entry->next;
610281806Srpaulo			pmksa_cache_free_entry(pmksa, prev);
611281806Srpaulo			continue;
612281806Srpaulo		}
613281806Srpaulo		entry = entry->next;
614281806Srpaulo	}
615281806Srpaulo
616281806Srpaulo	return found ? 0 : -1;
617281806Srpaulo}
618337817Scy
619337817Scy
620337817Scy/**
621337817Scy * pmksa_cache_auth_list - Dump text list of entries in PMKSA cache
622337817Scy * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
623337817Scy * @buf: Buffer for the list
624337817Scy * @len: Length of the buffer
625337817Scy * Returns: Number of bytes written to buffer
626337817Scy *
627337817Scy * This function is used to generate a text format representation of the
628337817Scy * current PMKSA cache contents for the ctrl_iface PMKSA command.
629337817Scy */
630337817Scyint pmksa_cache_auth_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len)
631337817Scy{
632337817Scy	int i, ret;
633337817Scy	char *pos = buf;
634337817Scy	struct rsn_pmksa_cache_entry *entry;
635337817Scy	struct os_reltime now;
636337817Scy
637337817Scy	os_get_reltime(&now);
638337817Scy	ret = os_snprintf(pos, buf + len - pos,
639337817Scy			  "Index / SPA / PMKID / expiration (in seconds) / opportunistic\n");
640337817Scy	if (os_snprintf_error(buf + len - pos, ret))
641337817Scy		return pos - buf;
642337817Scy	pos += ret;
643337817Scy	i = 0;
644337817Scy	entry = pmksa->pmksa;
645337817Scy	while (entry) {
646337817Scy		ret = os_snprintf(pos, buf + len - pos, "%d " MACSTR " ",
647337817Scy				  i, MAC2STR(entry->spa));
648337817Scy		if (os_snprintf_error(buf + len - pos, ret))
649337817Scy			return pos - buf;
650337817Scy		pos += ret;
651337817Scy		pos += wpa_snprintf_hex(pos, buf + len - pos, entry->pmkid,
652337817Scy					PMKID_LEN);
653337817Scy		ret = os_snprintf(pos, buf + len - pos, " %d %d\n",
654337817Scy				  (int) (entry->expiration - now.sec),
655337817Scy				  entry->opportunistic);
656337817Scy		if (os_snprintf_error(buf + len - pos, ret))
657337817Scy			return pos - buf;
658337817Scy		pos += ret;
659337817Scy		entry = entry->next;
660337817Scy	}
661337817Scy	return pos - buf;
662337817Scy}
663346981Scy
664346981Scy
665346981Scy#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
666346981Scy#ifdef CONFIG_MESH
667346981Scy
668346981Scy/**
669346981Scy * pmksa_cache_auth_list_mesh - Dump text list of entries in PMKSA cache
670346981Scy * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
671346981Scy * @addr: MAC address of the peer (NULL means any)
672346981Scy * @buf: Buffer for the list
673346981Scy * @len: Length of the buffer
674346981Scy * Returns: Number of bytes written to buffer
675346981Scy *
676346981Scy * This function is used to generate a text format representation of the
677346981Scy * current PMKSA cache contents for the ctrl_iface PMKSA_GET command to store
678346981Scy * in external storage.
679346981Scy */
680346981Scyint pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr,
681346981Scy			       char *buf, size_t len)
682346981Scy{
683346981Scy	int ret;
684346981Scy	char *pos, *end;
685346981Scy	struct rsn_pmksa_cache_entry *entry;
686346981Scy	struct os_reltime now;
687346981Scy
688346981Scy	pos = buf;
689346981Scy	end = buf + len;
690346981Scy	os_get_reltime(&now);
691346981Scy
692346981Scy
693346981Scy	/*
694346981Scy	 * Entry format:
695346981Scy	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
696346981Scy	 */
697346981Scy	for (entry = pmksa->pmksa; entry; entry = entry->next) {
698346981Scy		if (addr && os_memcmp(entry->spa, addr, ETH_ALEN) != 0)
699346981Scy			continue;
700346981Scy
701346981Scy		ret = os_snprintf(pos, end - pos, MACSTR " ",
702346981Scy				  MAC2STR(entry->spa));
703346981Scy		if (os_snprintf_error(end - pos, ret))
704346981Scy			return 0;
705346981Scy		pos += ret;
706346981Scy
707346981Scy		pos += wpa_snprintf_hex(pos, end - pos, entry->pmkid,
708346981Scy					PMKID_LEN);
709346981Scy
710346981Scy		ret = os_snprintf(pos, end - pos, " ");
711346981Scy		if (os_snprintf_error(end - pos, ret))
712346981Scy			return 0;
713346981Scy		pos += ret;
714346981Scy
715346981Scy		pos += wpa_snprintf_hex(pos, end - pos, entry->pmk,
716346981Scy					entry->pmk_len);
717346981Scy
718346981Scy		ret = os_snprintf(pos, end - pos, " %d\n",
719346981Scy				  (int) (entry->expiration - now.sec));
720346981Scy		if (os_snprintf_error(end - pos, ret))
721346981Scy			return 0;
722346981Scy		pos += ret;
723346981Scy	}
724346981Scy
725346981Scy	return pos - buf;
726346981Scy}
727346981Scy
728346981Scy#endif /* CONFIG_MESH */
729346981Scy#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */
730