]>
Commit | Line | Data |
---|---|---|
6fc6879b JM |
1 | /* |
2 | * hostapd - PMKSA cache for IEEE 802.11i RSN | |
5e3b5197 | 3 | * Copyright (c) 2004-2008, 2012-2015, Jouni Malinen <j@w1.fi> |
6fc6879b | 4 | * |
0f3d578e JM |
5 | * This software may be distributed under the terms of the BSD license. |
6 | * See README for more details. | |
6fc6879b JM |
7 | */ |
8 | ||
6226e38d | 9 | #include "utils/includes.h" |
6fc6879b | 10 | |
6226e38d JM |
11 | #include "utils/common.h" |
12 | #include "utils/eloop.h" | |
281c950b | 13 | #include "eapol_auth/eapol_auth_sm.h" |
e0e14a7b | 14 | #include "eapol_auth/eapol_auth_sm_i.h" |
cbc210de | 15 | #include "radius/radius_das.h" |
6226e38d JM |
16 | #include "sta_info.h" |
17 | #include "ap_config.h" | |
18 | #include "pmksa_cache_auth.h" | |
6fc6879b JM |
19 | |
20 | ||
21 | static const int pmksa_cache_max_entries = 1024; | |
22 | static const int dot11RSNAConfigPMKLifetime = 43200; | |
23 | ||
24 | struct rsn_pmksa_cache { | |
25 | #define PMKID_HASH_SIZE 128 | |
26 | #define PMKID_HASH(pmkid) (unsigned int) ((pmkid)[0] & 0x7f) | |
27 | struct rsn_pmksa_cache_entry *pmkid[PMKID_HASH_SIZE]; | |
28 | struct rsn_pmksa_cache_entry *pmksa; | |
29 | int pmksa_count; | |
30 | ||
31 | void (*free_cb)(struct rsn_pmksa_cache_entry *entry, void *ctx); | |
32 | void *ctx; | |
33 | }; | |
34 | ||
35 | ||
6fc6879b JM |
36 | static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa); |
37 | ||
38 | ||
39 | static void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry) | |
40 | { | |
1889af2e | 41 | os_free(entry->vlan_desc); |
6fc6879b | 42 | os_free(entry->identity); |
182748e4 | 43 | wpabuf_free(entry->cui); |
74784010 | 44 | #ifndef CONFIG_NO_RADIUS |
010dc068 | 45 | radius_free_class(&entry->radius_class); |
74784010 | 46 | #endif /* CONFIG_NO_RADIUS */ |
cb129db3 | 47 | bin_clear_free(entry, sizeof(*entry)); |
6fc6879b JM |
48 | } |
49 | ||
50 | ||
901d1fe1 JM |
51 | void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, |
52 | struct rsn_pmksa_cache_entry *entry) | |
6fc6879b JM |
53 | { |
54 | struct rsn_pmksa_cache_entry *pos, *prev; | |
a61fcc13 | 55 | unsigned int hash; |
6fc6879b JM |
56 | |
57 | pmksa->pmksa_count--; | |
58 | pmksa->free_cb(entry, pmksa->ctx); | |
a61fcc13 JM |
59 | |
60 | /* unlink from hash list */ | |
61 | hash = PMKID_HASH(entry->pmkid); | |
62 | pos = pmksa->pmkid[hash]; | |
6fc6879b JM |
63 | prev = NULL; |
64 | while (pos) { | |
65 | if (pos == entry) { | |
a61fcc13 JM |
66 | if (prev != NULL) |
67 | prev->hnext = entry->hnext; | |
68 | else | |
69 | pmksa->pmkid[hash] = entry->hnext; | |
6fc6879b JM |
70 | break; |
71 | } | |
72 | prev = pos; | |
73 | pos = pos->hnext; | |
74 | } | |
75 | ||
a61fcc13 | 76 | /* unlink from entry list */ |
6fc6879b JM |
77 | pos = pmksa->pmksa; |
78 | prev = NULL; | |
79 | while (pos) { | |
80 | if (pos == entry) { | |
81 | if (prev != NULL) | |
a61fcc13 | 82 | prev->next = entry->next; |
6fc6879b | 83 | else |
a61fcc13 | 84 | pmksa->pmksa = entry->next; |
6fc6879b JM |
85 | break; |
86 | } | |
87 | prev = pos; | |
88 | pos = pos->next; | |
89 | } | |
a61fcc13 | 90 | |
6fc6879b JM |
91 | _pmksa_cache_free_entry(entry); |
92 | } | |
93 | ||
94 | ||
4c522c77 MH |
95 | /** |
96 | * pmksa_cache_auth_flush - Flush all PMKSA cache entries | |
97 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() | |
98 | */ | |
99 | void pmksa_cache_auth_flush(struct rsn_pmksa_cache *pmksa) | |
100 | { | |
101 | while (pmksa->pmksa) { | |
102 | wpa_printf(MSG_DEBUG, "RSN: Flush PMKSA cache entry for " | |
103 | MACSTR, MAC2STR(pmksa->pmksa->spa)); | |
104 | pmksa_cache_free_entry(pmksa, pmksa->pmksa); | |
105 | } | |
106 | } | |
107 | ||
108 | ||
6fc6879b JM |
109 | static void pmksa_cache_expire(void *eloop_ctx, void *timeout_ctx) |
110 | { | |
111 | struct rsn_pmksa_cache *pmksa = eloop_ctx; | |
dd4e32ba | 112 | struct os_reltime now; |
6fc6879b | 113 | |
dd4e32ba | 114 | os_get_reltime(&now); |
6fc6879b | 115 | while (pmksa->pmksa && pmksa->pmksa->expiration <= now.sec) { |
6fc6879b | 116 | wpa_printf(MSG_DEBUG, "RSN: expired PMKSA cache entry for " |
b7593d35 JM |
117 | MACSTR, MAC2STR(pmksa->pmksa->spa)); |
118 | pmksa_cache_free_entry(pmksa, pmksa->pmksa); | |
6fc6879b JM |
119 | } |
120 | ||
121 | pmksa_cache_set_expiration(pmksa); | |
122 | } | |
123 | ||
124 | ||
125 | static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa) | |
126 | { | |
127 | int sec; | |
dd4e32ba | 128 | struct os_reltime now; |
6fc6879b JM |
129 | |
130 | eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL); | |
131 | if (pmksa->pmksa == NULL) | |
132 | return; | |
dd4e32ba | 133 | os_get_reltime(&now); |
6fc6879b JM |
134 | sec = pmksa->pmksa->expiration - now.sec; |
135 | if (sec < 0) | |
136 | sec = 0; | |
137 | eloop_register_timeout(sec + 1, 0, pmksa_cache_expire, pmksa, NULL); | |
138 | } | |
139 | ||
140 | ||
141 | static void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache_entry *entry, | |
142 | struct eapol_state_machine *eapol) | |
143 | { | |
1889af2e MB |
144 | struct vlan_description *vlan_desc; |
145 | ||
6fc6879b JM |
146 | if (eapol == NULL) |
147 | return; | |
148 | ||
149 | if (eapol->identity) { | |
150 | entry->identity = os_malloc(eapol->identity_len); | |
151 | if (entry->identity) { | |
152 | entry->identity_len = eapol->identity_len; | |
153 | os_memcpy(entry->identity, eapol->identity, | |
154 | eapol->identity_len); | |
155 | } | |
156 | } | |
157 | ||
182748e4 JM |
158 | if (eapol->radius_cui) |
159 | entry->cui = wpabuf_dup(eapol->radius_cui); | |
160 | ||
74784010 | 161 | #ifndef CONFIG_NO_RADIUS |
010dc068 | 162 | radius_copy_class(&entry->radius_class, &eapol->radius_class); |
74784010 | 163 | #endif /* CONFIG_NO_RADIUS */ |
6fc6879b JM |
164 | |
165 | entry->eap_type_authsrv = eapol->eap_type_authsrv; | |
1889af2e MB |
166 | |
167 | vlan_desc = ((struct sta_info *) eapol->sta)->vlan_desc; | |
168 | if (vlan_desc && vlan_desc->notempty) { | |
169 | entry->vlan_desc = os_zalloc(sizeof(struct vlan_description)); | |
170 | if (entry->vlan_desc) | |
171 | *entry->vlan_desc = *vlan_desc; | |
172 | } else { | |
173 | entry->vlan_desc = NULL; | |
174 | } | |
fcc306e3 | 175 | |
d72a0053 | 176 | entry->acct_multi_session_id = eapol->acct_multi_session_id; |
6fc6879b JM |
177 | } |
178 | ||
179 | ||
1889af2e MB |
180 | void pmksa_cache_to_eapol_data(struct hostapd_data *hapd, |
181 | struct rsn_pmksa_cache_entry *entry, | |
6fc6879b JM |
182 | struct eapol_state_machine *eapol) |
183 | { | |
184 | if (entry == NULL || eapol == NULL) | |
185 | return; | |
186 | ||
187 | if (entry->identity) { | |
188 | os_free(eapol->identity); | |
189 | eapol->identity = os_malloc(entry->identity_len); | |
190 | if (eapol->identity) { | |
191 | eapol->identity_len = entry->identity_len; | |
192 | os_memcpy(eapol->identity, entry->identity, | |
193 | entry->identity_len); | |
194 | } | |
195 | wpa_hexdump_ascii(MSG_DEBUG, "STA identity from PMKSA", | |
196 | eapol->identity, eapol->identity_len); | |
197 | } | |
198 | ||
182748e4 JM |
199 | if (entry->cui) { |
200 | wpabuf_free(eapol->radius_cui); | |
201 | eapol->radius_cui = wpabuf_dup(entry->cui); | |
202 | } | |
203 | ||
74784010 | 204 | #ifndef CONFIG_NO_RADIUS |
010dc068 JM |
205 | radius_free_class(&eapol->radius_class); |
206 | radius_copy_class(&eapol->radius_class, &entry->radius_class); | |
74784010 | 207 | #endif /* CONFIG_NO_RADIUS */ |
6fc6879b JM |
208 | if (eapol->radius_class.attr) { |
209 | wpa_printf(MSG_DEBUG, "Copied %lu Class attribute(s) from " | |
210 | "PMKSA", (unsigned long) eapol->radius_class.count); | |
211 | } | |
212 | ||
213 | eapol->eap_type_authsrv = entry->eap_type_authsrv; | |
90377029 JM |
214 | #ifndef CONFIG_NO_VLAN |
215 | ap_sta_set_vlan(hapd, eapol->sta, entry->vlan_desc); | |
216 | #endif /* CONFIG_NO_VLAN */ | |
fcc306e3 | 217 | |
d72a0053 | 218 | eapol->acct_multi_session_id = entry->acct_multi_session_id; |
6fc6879b JM |
219 | } |
220 | ||
221 | ||
bf98f7f3 JM |
222 | static void pmksa_cache_link_entry(struct rsn_pmksa_cache *pmksa, |
223 | struct rsn_pmksa_cache_entry *entry) | |
224 | { | |
225 | struct rsn_pmksa_cache_entry *pos, *prev; | |
a61fcc13 | 226 | int hash; |
bf98f7f3 JM |
227 | |
228 | /* Add the new entry; order by expiration time */ | |
229 | pos = pmksa->pmksa; | |
230 | prev = NULL; | |
231 | while (pos) { | |
232 | if (pos->expiration > entry->expiration) | |
233 | break; | |
234 | prev = pos; | |
235 | pos = pos->next; | |
236 | } | |
237 | if (prev == NULL) { | |
238 | entry->next = pmksa->pmksa; | |
239 | pmksa->pmksa = entry; | |
240 | } else { | |
241 | entry->next = prev->next; | |
242 | prev->next = entry; | |
243 | } | |
a61fcc13 JM |
244 | |
245 | hash = PMKID_HASH(entry->pmkid); | |
246 | entry->hnext = pmksa->pmkid[hash]; | |
247 | pmksa->pmkid[hash] = entry; | |
bf98f7f3 JM |
248 | |
249 | pmksa->pmksa_count++; | |
2e8483bf JM |
250 | if (prev == NULL) |
251 | pmksa_cache_set_expiration(pmksa); | |
bf98f7f3 JM |
252 | wpa_printf(MSG_DEBUG, "RSN: added PMKSA cache entry for " MACSTR, |
253 | MAC2STR(entry->spa)); | |
254 | wpa_hexdump(MSG_DEBUG, "RSN: added PMKID", entry->pmkid, PMKID_LEN); | |
255 | } | |
256 | ||
257 | ||
6fc6879b | 258 | /** |
4bb081f1 JM |
259 | * pmksa_cache_auth_add - Add a PMKSA cache entry |
260 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() | |
6fc6879b JM |
261 | * @pmk: The new pairwise master key |
262 | * @pmk_len: PMK length in bytes, usually PMK_LEN (32) | |
70c93963 | 263 | * @pmkid: Calculated PMKID |
087a1f4e JM |
264 | * @kck: Key confirmation key or %NULL if not yet derived |
265 | * @kck_len: KCK length in bytes | |
6fc6879b JM |
266 | * @aa: Authenticator address |
267 | * @spa: Supplicant address | |
268 | * @session_timeout: Session timeout | |
269 | * @eapol: Pointer to EAPOL state machine data | |
56586197 | 270 | * @akmp: WPA_KEY_MGMT_* used in key derivation |
6fc6879b JM |
271 | * Returns: Pointer to the added PMKSA cache entry or %NULL on error |
272 | * | |
273 | * This function create a PMKSA entry for a new PMK and adds it to the PMKSA | |
274 | * cache. If an old entry is already in the cache for the same Supplicant, | |
275 | * this entry will be replaced with the new entry. PMKID will be calculated | |
276 | * based on the PMK. | |
277 | */ | |
278 | struct rsn_pmksa_cache_entry * | |
4bb081f1 | 279 | pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa, |
70c93963 | 280 | const u8 *pmk, size_t pmk_len, const u8 *pmkid, |
087a1f4e JM |
281 | const u8 *kck, size_t kck_len, |
282 | const u8 *aa, const u8 *spa, int session_timeout, | |
283 | struct eapol_state_machine *eapol, int akmp) | |
6fc6879b | 284 | { |
4d77d80e MH |
285 | struct rsn_pmksa_cache_entry *entry; |
286 | ||
287 | entry = pmksa_cache_auth_create_entry(pmk, pmk_len, pmkid, kck, kck_len, | |
288 | aa, spa, session_timeout, eapol, | |
289 | akmp); | |
290 | ||
291 | if (pmksa_cache_auth_add_entry(pmksa, entry) < 0) | |
292 | return NULL; | |
293 | ||
294 | return entry; | |
295 | } | |
296 | ||
297 | ||
298 | /** | |
299 | * pmksa_cache_auth_create_entry - Create a PMKSA cache entry | |
300 | * @pmk: The new pairwise master key | |
301 | * @pmk_len: PMK length in bytes, usually PMK_LEN (32) | |
302 | * @pmkid: Calculated PMKID | |
303 | * @kck: Key confirmation key or %NULL if not yet derived | |
304 | * @kck_len: KCK length in bytes | |
305 | * @aa: Authenticator address | |
306 | * @spa: Supplicant address | |
307 | * @session_timeout: Session timeout | |
308 | * @eapol: Pointer to EAPOL state machine data | |
309 | * @akmp: WPA_KEY_MGMT_* used in key derivation | |
310 | * Returns: Pointer to the added PMKSA cache entry or %NULL on error | |
311 | * | |
312 | * This function creates a PMKSA entry. | |
313 | */ | |
314 | struct rsn_pmksa_cache_entry * | |
315 | pmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid, | |
316 | const u8 *kck, size_t kck_len, const u8 *aa, | |
317 | const u8 *spa, int session_timeout, | |
318 | struct eapol_state_machine *eapol, int akmp) | |
319 | { | |
320 | struct rsn_pmksa_cache_entry *entry; | |
dd4e32ba | 321 | struct os_reltime now; |
6fc6879b | 322 | |
207976f0 | 323 | if (pmk_len > PMK_LEN_MAX) |
6fc6879b JM |
324 | return NULL; |
325 | ||
087a1f4e JM |
326 | if (wpa_key_mgmt_suite_b(akmp) && !kck) |
327 | return NULL; | |
328 | ||
6fc6879b JM |
329 | entry = os_zalloc(sizeof(*entry)); |
330 | if (entry == NULL) | |
331 | return NULL; | |
332 | os_memcpy(entry->pmk, pmk, pmk_len); | |
333 | entry->pmk_len = pmk_len; | |
70c93963 MH |
334 | if (pmkid) |
335 | os_memcpy(entry->pmkid, pmkid, PMKID_LEN); | |
336 | else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192) | |
5e3b5197 JM |
337 | rsn_pmkid_suite_b_192(kck, kck_len, aa, spa, entry->pmkid); |
338 | else if (wpa_key_mgmt_suite_b(akmp)) | |
087a1f4e JM |
339 | rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid); |
340 | else | |
41b81914 | 341 | rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, akmp); |
dd4e32ba | 342 | os_get_reltime(&now); |
6fc6879b JM |
343 | entry->expiration = now.sec; |
344 | if (session_timeout > 0) | |
345 | entry->expiration += session_timeout; | |
346 | else | |
347 | entry->expiration += dot11RSNAConfigPMKLifetime; | |
56586197 | 348 | entry->akmp = akmp; |
6fc6879b JM |
349 | os_memcpy(entry->spa, spa, ETH_ALEN); |
350 | pmksa_cache_from_eapol_data(entry, eapol); | |
351 | ||
4d77d80e MH |
352 | return entry; |
353 | } | |
354 | ||
355 | ||
356 | /** | |
357 | * pmksa_cache_auth_add_entry - Add a PMKSA cache entry | |
358 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() | |
359 | * @entry: Pointer to PMKSA cache entry | |
360 | * | |
361 | * This function adds PMKSA cache entry to the PMKSA cache. If an old entry is | |
362 | * already in the cache for the same Supplicant, this entry will be replaced | |
363 | * with the new entry. PMKID will be calculated based on the PMK. | |
364 | */ | |
365 | int pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa, | |
366 | struct rsn_pmksa_cache_entry *entry) | |
367 | { | |
368 | struct rsn_pmksa_cache_entry *pos; | |
369 | ||
370 | if (entry == NULL) | |
371 | return -1; | |
372 | ||
6fc6879b JM |
373 | /* Replace an old entry for the same STA (if found) with the new entry |
374 | */ | |
4d77d80e | 375 | pos = pmksa_cache_auth_get(pmksa, entry->spa, NULL); |
6fc6879b JM |
376 | if (pos) |
377 | pmksa_cache_free_entry(pmksa, pos); | |
378 | ||
379 | if (pmksa->pmksa_count >= pmksa_cache_max_entries && pmksa->pmksa) { | |
380 | /* Remove the oldest entry to make room for the new entry */ | |
381 | wpa_printf(MSG_DEBUG, "RSN: removed the oldest PMKSA cache " | |
382 | "entry (for " MACSTR ") to make room for new one", | |
383 | MAC2STR(pmksa->pmksa->spa)); | |
384 | pmksa_cache_free_entry(pmksa, pmksa->pmksa); | |
385 | } | |
386 | ||
bf98f7f3 JM |
387 | pmksa_cache_link_entry(pmksa, entry); |
388 | ||
4d77d80e | 389 | return 0; |
bf98f7f3 JM |
390 | } |
391 | ||
392 | ||
393 | struct rsn_pmksa_cache_entry * | |
394 | pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa, | |
395 | const struct rsn_pmksa_cache_entry *old_entry, | |
396 | const u8 *aa, const u8 *pmkid) | |
397 | { | |
398 | struct rsn_pmksa_cache_entry *entry; | |
399 | ||
400 | entry = os_zalloc(sizeof(*entry)); | |
401 | if (entry == NULL) | |
402 | return NULL; | |
403 | os_memcpy(entry->pmkid, pmkid, PMKID_LEN); | |
404 | os_memcpy(entry->pmk, old_entry->pmk, old_entry->pmk_len); | |
405 | entry->pmk_len = old_entry->pmk_len; | |
406 | entry->expiration = old_entry->expiration; | |
407 | entry->akmp = old_entry->akmp; | |
408 | os_memcpy(entry->spa, old_entry->spa, ETH_ALEN); | |
409 | entry->opportunistic = 1; | |
410 | if (old_entry->identity) { | |
411 | entry->identity = os_malloc(old_entry->identity_len); | |
412 | if (entry->identity) { | |
413 | entry->identity_len = old_entry->identity_len; | |
414 | os_memcpy(entry->identity, old_entry->identity, | |
415 | old_entry->identity_len); | |
416 | } | |
6fc6879b | 417 | } |
182748e4 JM |
418 | if (old_entry->cui) |
419 | entry->cui = wpabuf_dup(old_entry->cui); | |
74784010 | 420 | #ifndef CONFIG_NO_RADIUS |
010dc068 | 421 | radius_copy_class(&entry->radius_class, &old_entry->radius_class); |
74784010 | 422 | #endif /* CONFIG_NO_RADIUS */ |
bf98f7f3 | 423 | entry->eap_type_authsrv = old_entry->eap_type_authsrv; |
1889af2e MB |
424 | if (old_entry->vlan_desc) { |
425 | entry->vlan_desc = os_zalloc(sizeof(struct vlan_description)); | |
426 | if (entry->vlan_desc) | |
427 | *entry->vlan_desc = *old_entry->vlan_desc; | |
428 | } else { | |
429 | entry->vlan_desc = NULL; | |
430 | } | |
bf98f7f3 | 431 | entry->opportunistic = 1; |
6fc6879b | 432 | |
bf98f7f3 | 433 | pmksa_cache_link_entry(pmksa, entry); |
6fc6879b JM |
434 | |
435 | return entry; | |
436 | } | |
437 | ||
438 | ||
439 | /** | |
4bb081f1 JM |
440 | * pmksa_cache_auth_deinit - Free all entries in PMKSA cache |
441 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() | |
6fc6879b | 442 | */ |
4bb081f1 | 443 | void pmksa_cache_auth_deinit(struct rsn_pmksa_cache *pmksa) |
6fc6879b JM |
444 | { |
445 | struct rsn_pmksa_cache_entry *entry, *prev; | |
446 | int i; | |
447 | ||
448 | if (pmksa == NULL) | |
449 | return; | |
450 | ||
451 | entry = pmksa->pmksa; | |
452 | while (entry) { | |
453 | prev = entry; | |
454 | entry = entry->next; | |
455 | _pmksa_cache_free_entry(prev); | |
456 | } | |
457 | eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL); | |
a61fcc13 JM |
458 | pmksa->pmksa_count = 0; |
459 | pmksa->pmksa = NULL; | |
6fc6879b JM |
460 | for (i = 0; i < PMKID_HASH_SIZE; i++) |
461 | pmksa->pmkid[i] = NULL; | |
462 | os_free(pmksa); | |
463 | } | |
464 | ||
465 | ||
466 | /** | |
4bb081f1 JM |
467 | * pmksa_cache_auth_get - Fetch a PMKSA cache entry |
468 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() | |
6fc6879b JM |
469 | * @spa: Supplicant address or %NULL to match any |
470 | * @pmkid: PMKID or %NULL to match any | |
471 | * Returns: Pointer to PMKSA cache entry or %NULL if no match was found | |
472 | */ | |
4bb081f1 JM |
473 | struct rsn_pmksa_cache_entry * |
474 | pmksa_cache_auth_get(struct rsn_pmksa_cache *pmksa, | |
475 | const u8 *spa, const u8 *pmkid) | |
6fc6879b JM |
476 | { |
477 | struct rsn_pmksa_cache_entry *entry; | |
478 | ||
a61fcc13 JM |
479 | if (pmkid) { |
480 | for (entry = pmksa->pmkid[PMKID_HASH(pmkid)]; entry; | |
481 | entry = entry->hnext) { | |
482 | if ((spa == NULL || | |
483 | os_memcmp(entry->spa, spa, ETH_ALEN) == 0) && | |
484 | os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0) | |
485 | return entry; | |
486 | } | |
487 | } else { | |
488 | for (entry = pmksa->pmksa; entry; entry = entry->next) { | |
489 | if (spa == NULL || | |
490 | os_memcmp(entry->spa, spa, ETH_ALEN) == 0) | |
491 | return entry; | |
492 | } | |
6fc6879b | 493 | } |
a61fcc13 | 494 | |
6fc6879b JM |
495 | return NULL; |
496 | } | |
497 | ||
498 | ||
bf98f7f3 JM |
499 | /** |
500 | * pmksa_cache_get_okc - Fetch a PMKSA cache entry using OKC | |
4bb081f1 | 501 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() |
a17df5fb | 502 | * @aa: Authenticator address |
bf98f7f3 JM |
503 | * @spa: Supplicant address |
504 | * @pmkid: PMKID | |
505 | * Returns: Pointer to PMKSA cache entry or %NULL if no match was found | |
506 | * | |
507 | * Use opportunistic key caching (OKC) to find a PMK for a supplicant. | |
508 | */ | |
509 | struct rsn_pmksa_cache_entry * pmksa_cache_get_okc( | |
510 | struct rsn_pmksa_cache *pmksa, const u8 *aa, const u8 *spa, | |
511 | const u8 *pmkid) | |
512 | { | |
513 | struct rsn_pmksa_cache_entry *entry; | |
514 | u8 new_pmkid[PMKID_LEN]; | |
515 | ||
9c829900 | 516 | for (entry = pmksa->pmksa; entry; entry = entry->next) { |
bf98f7f3 JM |
517 | if (os_memcmp(entry->spa, spa, ETH_ALEN) != 0) |
518 | continue; | |
56586197 | 519 | rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa, new_pmkid, |
41b81914 | 520 | entry->akmp); |
bf98f7f3 JM |
521 | if (os_memcmp(new_pmkid, pmkid, PMKID_LEN) == 0) |
522 | return entry; | |
bf98f7f3 JM |
523 | } |
524 | return NULL; | |
525 | } | |
526 | ||
527 | ||
6fc6879b | 528 | /** |
4bb081f1 | 529 | * pmksa_cache_auth_init - Initialize PMKSA cache |
6fc6879b JM |
530 | * @free_cb: Callback function to be called when a PMKSA cache entry is freed |
531 | * @ctx: Context pointer for free_cb function | |
532 | * Returns: Pointer to PMKSA cache data or %NULL on failure | |
533 | */ | |
534 | struct rsn_pmksa_cache * | |
4bb081f1 JM |
535 | pmksa_cache_auth_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry, |
536 | void *ctx), void *ctx) | |
6fc6879b JM |
537 | { |
538 | struct rsn_pmksa_cache *pmksa; | |
539 | ||
540 | pmksa = os_zalloc(sizeof(*pmksa)); | |
541 | if (pmksa) { | |
542 | pmksa->free_cb = free_cb; | |
543 | pmksa->ctx = ctx; | |
544 | } | |
545 | ||
546 | return pmksa; | |
547 | } | |
cbc210de JM |
548 | |
549 | ||
550 | static int das_attr_match(struct rsn_pmksa_cache_entry *entry, | |
551 | struct radius_das_attrs *attr) | |
552 | { | |
553 | int match = 0; | |
554 | ||
555 | if (attr->sta_addr) { | |
556 | if (os_memcmp(attr->sta_addr, entry->spa, ETH_ALEN) != 0) | |
557 | return 0; | |
558 | match++; | |
559 | } | |
560 | ||
561 | if (attr->acct_multi_session_id) { | |
562 | char buf[20]; | |
563 | ||
d72a0053 | 564 | if (attr->acct_multi_session_id_len != 16) |
cbc210de | 565 | return 0; |
1492fbb9 NL |
566 | os_snprintf(buf, sizeof(buf), "%016llX", |
567 | (unsigned long long) entry->acct_multi_session_id); | |
d72a0053 | 568 | if (os_memcmp(attr->acct_multi_session_id, buf, 16) != 0) |
cbc210de JM |
569 | return 0; |
570 | match++; | |
571 | } | |
572 | ||
573 | if (attr->cui) { | |
574 | if (!entry->cui || | |
575 | attr->cui_len != wpabuf_len(entry->cui) || | |
576 | os_memcmp(attr->cui, wpabuf_head(entry->cui), | |
577 | attr->cui_len) != 0) | |
578 | return 0; | |
579 | match++; | |
580 | } | |
581 | ||
582 | if (attr->user_name) { | |
583 | if (!entry->identity || | |
584 | attr->user_name_len != entry->identity_len || | |
585 | os_memcmp(attr->user_name, entry->identity, | |
586 | attr->user_name_len) != 0) | |
587 | return 0; | |
588 | match++; | |
589 | } | |
590 | ||
591 | return match; | |
592 | } | |
593 | ||
594 | ||
595 | int pmksa_cache_auth_radius_das_disconnect(struct rsn_pmksa_cache *pmksa, | |
596 | struct radius_das_attrs *attr) | |
597 | { | |
598 | int found = 0; | |
599 | struct rsn_pmksa_cache_entry *entry, *prev; | |
600 | ||
601 | if (attr->acct_session_id) | |
602 | return -1; | |
603 | ||
604 | entry = pmksa->pmksa; | |
605 | while (entry) { | |
606 | if (das_attr_match(entry, attr)) { | |
607 | found++; | |
608 | prev = entry; | |
609 | entry = entry->next; | |
610 | pmksa_cache_free_entry(pmksa, prev); | |
611 | continue; | |
612 | } | |
613 | entry = entry->next; | |
614 | } | |
615 | ||
616 | return found ? 0 : -1; | |
617 | } | |
b8daac18 MH |
618 | |
619 | ||
620 | /** | |
621 | * pmksa_cache_auth_list - Dump text list of entries in PMKSA cache | |
622 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() | |
623 | * @buf: Buffer for the list | |
624 | * @len: Length of the buffer | |
625 | * Returns: Number of bytes written to buffer | |
626 | * | |
627 | * This function is used to generate a text format representation of the | |
628 | * current PMKSA cache contents for the ctrl_iface PMKSA command. | |
629 | */ | |
630 | int pmksa_cache_auth_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len) | |
631 | { | |
632 | int i, ret; | |
633 | char *pos = buf; | |
634 | struct rsn_pmksa_cache_entry *entry; | |
635 | struct os_reltime now; | |
636 | ||
637 | os_get_reltime(&now); | |
638 | ret = os_snprintf(pos, buf + len - pos, | |
639 | "Index / SPA / PMKID / expiration (in seconds) / opportunistic\n"); | |
640 | if (os_snprintf_error(buf + len - pos, ret)) | |
641 | return pos - buf; | |
642 | pos += ret; | |
643 | i = 0; | |
644 | entry = pmksa->pmksa; | |
645 | while (entry) { | |
646 | ret = os_snprintf(pos, buf + len - pos, "%d " MACSTR " ", | |
647 | i, MAC2STR(entry->spa)); | |
648 | if (os_snprintf_error(buf + len - pos, ret)) | |
649 | return pos - buf; | |
650 | pos += ret; | |
651 | pos += wpa_snprintf_hex(pos, buf + len - pos, entry->pmkid, | |
652 | PMKID_LEN); | |
653 | ret = os_snprintf(pos, buf + len - pos, " %d %d\n", | |
654 | (int) (entry->expiration - now.sec), | |
655 | entry->opportunistic); | |
656 | if (os_snprintf_error(buf + len - pos, ret)) | |
657 | return pos - buf; | |
658 | pos += ret; | |
659 | entry = entry->next; | |
660 | } | |
661 | return pos - buf; | |
662 | } | |
4d77d80e MH |
663 | |
664 | ||
665 | #ifdef CONFIG_PMKSA_CACHE_EXTERNAL | |
666 | #ifdef CONFIG_MESH | |
667 | ||
668 | /** | |
669 | * pmksa_cache_auth_list_mesh - Dump text list of entries in PMKSA cache | |
670 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() | |
671 | * @addr: MAC address of the peer (NULL means any) | |
672 | * @buf: Buffer for the list | |
673 | * @len: Length of the buffer | |
674 | * Returns: Number of bytes written to buffer | |
675 | * | |
676 | * This function is used to generate a text format representation of the | |
677 | * current PMKSA cache contents for the ctrl_iface PMKSA_GET command to store | |
678 | * in external storage. | |
679 | */ | |
680 | int pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr, | |
681 | char *buf, size_t len) | |
682 | { | |
683 | int ret; | |
684 | char *pos, *end; | |
685 | struct rsn_pmksa_cache_entry *entry; | |
686 | struct os_reltime now; | |
687 | ||
688 | pos = buf; | |
689 | end = buf + len; | |
690 | os_get_reltime(&now); | |
691 | ||
692 | ||
693 | /* | |
694 | * Entry format: | |
695 | * <BSSID> <PMKID> <PMK> <expiration in seconds> | |
696 | */ | |
697 | for (entry = pmksa->pmksa; entry; entry = entry->next) { | |
698 | if (addr && os_memcmp(entry->spa, addr, ETH_ALEN) != 0) | |
699 | continue; | |
700 | ||
701 | ret = os_snprintf(pos, end - pos, MACSTR " ", | |
702 | MAC2STR(entry->spa)); | |
703 | if (os_snprintf_error(end - pos, ret)) | |
704 | return 0; | |
705 | pos += ret; | |
706 | ||
707 | pos += wpa_snprintf_hex(pos, end - pos, entry->pmkid, | |
708 | PMKID_LEN); | |
709 | ||
710 | ret = os_snprintf(pos, end - pos, " "); | |
711 | if (os_snprintf_error(end - pos, ret)) | |
712 | return 0; | |
713 | pos += ret; | |
714 | ||
715 | pos += wpa_snprintf_hex(pos, end - pos, entry->pmk, | |
716 | entry->pmk_len); | |
717 | ||
718 | ret = os_snprintf(pos, end - pos, " %d\n", | |
719 | (int) (entry->expiration - now.sec)); | |
720 | if (os_snprintf_error(end - pos, ret)) | |
721 | return 0; | |
722 | pos += ret; | |
723 | } | |
724 | ||
725 | return pos - buf; | |
726 | } | |
727 | ||
728 | #endif /* CONFIG_MESH */ | |
729 | #endif /* CONFIG_PMKSA_CACHE_EXTERNAL */ |