]>
Commit | Line | Data |
---|---|---|
6fc6879b JM |
1 | /* |
2 | * WPA Supplicant - RSN PMKSA cache | |
56586197 | 3 | * Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi> |
6fc6879b JM |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * Alternatively, this software may be distributed under the terms of BSD | |
10 | * license. | |
11 | * | |
12 | * See README and COPYING for more details. | |
13 | */ | |
14 | ||
15 | #include "includes.h" | |
16 | ||
17 | #include "common.h" | |
18 | #include "wpa.h" | |
19 | #include "eloop.h" | |
20 | #include "sha1.h" | |
56586197 | 21 | #include "sha256.h" |
6fc6879b JM |
22 | #include "wpa_i.h" |
23 | #include "eapol_supp/eapol_supp_sm.h" | |
24 | #include "pmksa_cache.h" | |
25 | ||
26 | #if defined(IEEE8021X_EAPOL) && !defined(CONFIG_NO_WPA2) | |
27 | ||
28 | static const int pmksa_cache_max_entries = 32; | |
29 | ||
30 | struct rsn_pmksa_cache { | |
31 | struct rsn_pmksa_cache_entry *pmksa; /* PMKSA cache */ | |
32 | int pmksa_count; /* number of entries in PMKSA cache */ | |
33 | struct wpa_sm *sm; /* TODO: get rid of this reference(?) */ | |
34 | ||
35 | void (*free_cb)(struct rsn_pmksa_cache_entry *entry, void *ctx, | |
36 | int replace); | |
37 | void *ctx; | |
38 | }; | |
39 | ||
40 | ||
41 | /** | |
42 | * rsn_pmkid - Calculate PMK identifier | |
43 | * @pmk: Pairwise master key | |
44 | * @pmk_len: Length of pmk in bytes | |
45 | * @aa: Authenticator address | |
46 | * @spa: Supplicant address | |
56586197 | 47 | * @use_sha256: Whether to use SHA256-based KDF |
6fc6879b JM |
48 | * |
49 | * IEEE Std 802.11i-2004 - 8.5.1.2 Pairwise key hierarchy | |
50 | * PMKID = HMAC-SHA1-128(PMK, "PMK Name" || AA || SPA) | |
51 | */ | |
56586197 JM |
52 | static void rsn_pmkid(const u8 *pmk, size_t pmk_len, const u8 *aa, |
53 | const u8 *spa, u8 *pmkid, int use_sha256) | |
6fc6879b JM |
54 | { |
55 | char *title = "PMK Name"; | |
56 | const u8 *addr[3]; | |
57 | const size_t len[3] = { 8, ETH_ALEN, ETH_ALEN }; | |
56586197 | 58 | unsigned char hash[SHA256_MAC_LEN]; |
6fc6879b JM |
59 | |
60 | addr[0] = (u8 *) title; | |
61 | addr[1] = aa; | |
62 | addr[2] = spa; | |
63 | ||
56586197 JM |
64 | #ifdef CONFIG_IEEE80211W |
65 | if (use_sha256) | |
66 | hmac_sha256_vector(pmk, pmk_len, 3, addr, len, hash); | |
67 | else | |
68 | #endif /* CONFIG_IEEE80211W */ | |
69 | hmac_sha1_vector(pmk, pmk_len, 3, addr, len, hash); | |
6fc6879b JM |
70 | os_memcpy(pmkid, hash, PMKID_LEN); |
71 | } | |
72 | ||
73 | ||
74 | static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa); | |
75 | ||
76 | ||
77 | static void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry) | |
78 | { | |
79 | os_free(entry); | |
80 | } | |
81 | ||
82 | ||
83 | static void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, | |
84 | struct rsn_pmksa_cache_entry *entry, | |
85 | int replace) | |
86 | { | |
87 | pmksa->pmksa_count--; | |
88 | pmksa->free_cb(entry, pmksa->ctx, replace); | |
89 | _pmksa_cache_free_entry(entry); | |
90 | } | |
91 | ||
92 | ||
93 | static void pmksa_cache_expire(void *eloop_ctx, void *timeout_ctx) | |
94 | { | |
95 | struct rsn_pmksa_cache *pmksa = eloop_ctx; | |
96 | struct os_time now; | |
97 | ||
98 | os_get_time(&now); | |
99 | while (pmksa->pmksa && pmksa->pmksa->expiration <= now.sec) { | |
100 | struct rsn_pmksa_cache_entry *entry = pmksa->pmksa; | |
101 | pmksa->pmksa = entry->next; | |
102 | wpa_printf(MSG_DEBUG, "RSN: expired PMKSA cache entry for " | |
103 | MACSTR, MAC2STR(entry->aa)); | |
104 | pmksa_cache_free_entry(pmksa, entry, 0); | |
105 | } | |
106 | ||
107 | pmksa_cache_set_expiration(pmksa); | |
108 | } | |
109 | ||
110 | ||
111 | static void pmksa_cache_reauth(void *eloop_ctx, void *timeout_ctx) | |
112 | { | |
113 | struct rsn_pmksa_cache *pmksa = eloop_ctx; | |
114 | pmksa->sm->cur_pmksa = NULL; | |
115 | eapol_sm_request_reauth(pmksa->sm->eapol); | |
116 | } | |
117 | ||
118 | ||
119 | static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa) | |
120 | { | |
121 | int sec; | |
122 | struct rsn_pmksa_cache_entry *entry; | |
123 | struct os_time now; | |
124 | ||
125 | eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL); | |
126 | eloop_cancel_timeout(pmksa_cache_reauth, pmksa, NULL); | |
127 | if (pmksa->pmksa == NULL) | |
128 | return; | |
129 | os_get_time(&now); | |
130 | sec = pmksa->pmksa->expiration - now.sec; | |
131 | if (sec < 0) | |
132 | sec = 0; | |
133 | eloop_register_timeout(sec + 1, 0, pmksa_cache_expire, pmksa, NULL); | |
134 | ||
135 | entry = pmksa->sm->cur_pmksa ? pmksa->sm->cur_pmksa : | |
136 | pmksa_cache_get(pmksa, pmksa->sm->bssid, NULL); | |
137 | if (entry) { | |
138 | sec = pmksa->pmksa->reauth_time - now.sec; | |
139 | if (sec < 0) | |
140 | sec = 0; | |
141 | eloop_register_timeout(sec, 0, pmksa_cache_reauth, pmksa, | |
142 | NULL); | |
143 | } | |
144 | } | |
145 | ||
146 | ||
147 | /** | |
148 | * pmksa_cache_add - Add a PMKSA cache entry | |
149 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
150 | * @pmk: The new pairwise master key | |
151 | * @pmk_len: PMK length in bytes, usually PMK_LEN (32) | |
152 | * @aa: Authenticator address | |
153 | * @spa: Supplicant address | |
154 | * @network_ctx: Network configuration context for this PMK | |
56586197 | 155 | * @akmp: WPA_KEY_MGMT_* used in key derivation |
6fc6879b JM |
156 | * Returns: Pointer to the added PMKSA cache entry or %NULL on error |
157 | * | |
158 | * This function create a PMKSA entry for a new PMK and adds it to the PMKSA | |
159 | * cache. If an old entry is already in the cache for the same Authenticator, | |
160 | * this entry will be replaced with the new entry. PMKID will be calculated | |
161 | * based on the PMK and the driver interface is notified of the new PMKID. | |
162 | */ | |
163 | struct rsn_pmksa_cache_entry * | |
164 | pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len, | |
56586197 | 165 | const u8 *aa, const u8 *spa, void *network_ctx, int akmp) |
6fc6879b JM |
166 | { |
167 | struct rsn_pmksa_cache_entry *entry, *pos, *prev; | |
168 | struct os_time now; | |
169 | ||
170 | if (pmksa->sm->proto != WPA_PROTO_RSN || pmk_len > PMK_LEN) | |
171 | return NULL; | |
172 | ||
173 | entry = os_zalloc(sizeof(*entry)); | |
174 | if (entry == NULL) | |
175 | return NULL; | |
176 | os_memcpy(entry->pmk, pmk, pmk_len); | |
177 | entry->pmk_len = pmk_len; | |
56586197 JM |
178 | rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, |
179 | wpa_key_mgmt_sha256(akmp)); | |
6fc6879b JM |
180 | os_get_time(&now); |
181 | entry->expiration = now.sec + pmksa->sm->dot11RSNAConfigPMKLifetime; | |
182 | entry->reauth_time = now.sec + pmksa->sm->dot11RSNAConfigPMKLifetime * | |
183 | pmksa->sm->dot11RSNAConfigPMKReauthThreshold / 100; | |
56586197 | 184 | entry->akmp = akmp; |
6fc6879b JM |
185 | os_memcpy(entry->aa, aa, ETH_ALEN); |
186 | entry->network_ctx = network_ctx; | |
187 | ||
188 | /* Replace an old entry for the same Authenticator (if found) with the | |
189 | * new entry */ | |
190 | pos = pmksa->pmksa; | |
191 | prev = NULL; | |
192 | while (pos) { | |
193 | if (os_memcmp(aa, pos->aa, ETH_ALEN) == 0) { | |
194 | if (pos->pmk_len == pmk_len && | |
195 | os_memcmp(pos->pmk, pmk, pmk_len) == 0 && | |
196 | os_memcmp(pos->pmkid, entry->pmkid, PMKID_LEN) == | |
197 | 0) { | |
198 | wpa_printf(MSG_DEBUG, "WPA: reusing previous " | |
199 | "PMKSA entry"); | |
200 | os_free(entry); | |
201 | return pos; | |
202 | } | |
203 | if (prev == NULL) | |
204 | pmksa->pmksa = pos->next; | |
205 | else | |
206 | prev->next = pos->next; | |
207 | if (pos == pmksa->sm->cur_pmksa) { | |
208 | /* We are about to replace the current PMKSA | |
209 | * cache entry. This happens when the PMKSA | |
210 | * caching attempt fails, so we don't want to | |
211 | * force pmksa_cache_free_entry() to disconnect | |
212 | * at this point. Let's just make sure the old | |
213 | * PMKSA cache entry will not be used in the | |
214 | * future. | |
215 | */ | |
216 | wpa_printf(MSG_DEBUG, "RSN: replacing current " | |
217 | "PMKSA entry"); | |
218 | pmksa->sm->cur_pmksa = NULL; | |
219 | } | |
220 | wpa_printf(MSG_DEBUG, "RSN: Replace PMKSA entry for " | |
221 | "the current AP"); | |
222 | pmksa_cache_free_entry(pmksa, pos, 1); | |
223 | break; | |
224 | } | |
225 | prev = pos; | |
226 | pos = pos->next; | |
227 | } | |
228 | ||
229 | if (pmksa->pmksa_count >= pmksa_cache_max_entries && pmksa->pmksa) { | |
230 | /* Remove the oldest entry to make room for the new entry */ | |
231 | pos = pmksa->pmksa; | |
232 | pmksa->pmksa = pos->next; | |
233 | wpa_printf(MSG_DEBUG, "RSN: removed the oldest PMKSA cache " | |
234 | "entry (for " MACSTR ") to make room for new one", | |
235 | MAC2STR(pos->aa)); | |
236 | wpa_sm_remove_pmkid(pmksa->sm, pos->aa, pos->pmkid); | |
237 | pmksa_cache_free_entry(pmksa, pos, 0); | |
238 | } | |
239 | ||
240 | /* Add the new entry; order by expiration time */ | |
241 | pos = pmksa->pmksa; | |
242 | prev = NULL; | |
243 | while (pos) { | |
244 | if (pos->expiration > entry->expiration) | |
245 | break; | |
246 | prev = pos; | |
247 | pos = pos->next; | |
248 | } | |
249 | if (prev == NULL) { | |
250 | entry->next = pmksa->pmksa; | |
251 | pmksa->pmksa = entry; | |
252 | pmksa_cache_set_expiration(pmksa); | |
253 | } else { | |
254 | entry->next = prev->next; | |
255 | prev->next = entry; | |
256 | } | |
257 | pmksa->pmksa_count++; | |
258 | wpa_printf(MSG_DEBUG, "RSN: added PMKSA cache entry for " MACSTR, | |
259 | MAC2STR(entry->aa)); | |
260 | wpa_sm_add_pmkid(pmksa->sm, entry->aa, entry->pmkid); | |
261 | ||
262 | return entry; | |
263 | } | |
264 | ||
265 | ||
266 | /** | |
267 | * pmksa_cache_deinit - Free all entries in PMKSA cache | |
268 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
269 | */ | |
270 | void pmksa_cache_deinit(struct rsn_pmksa_cache *pmksa) | |
271 | { | |
272 | struct rsn_pmksa_cache_entry *entry, *prev; | |
273 | ||
274 | if (pmksa == NULL) | |
275 | return; | |
276 | ||
277 | entry = pmksa->pmksa; | |
278 | pmksa->pmksa = NULL; | |
279 | while (entry) { | |
280 | prev = entry; | |
281 | entry = entry->next; | |
282 | os_free(prev); | |
283 | } | |
284 | pmksa_cache_set_expiration(pmksa); | |
285 | os_free(pmksa); | |
286 | } | |
287 | ||
288 | ||
289 | /** | |
290 | * pmksa_cache_get - Fetch a PMKSA cache entry | |
291 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
292 | * @aa: Authenticator address or %NULL to match any | |
293 | * @pmkid: PMKID or %NULL to match any | |
294 | * Returns: Pointer to PMKSA cache entry or %NULL if no match was found | |
295 | */ | |
296 | struct rsn_pmksa_cache_entry * pmksa_cache_get(struct rsn_pmksa_cache *pmksa, | |
297 | const u8 *aa, const u8 *pmkid) | |
298 | { | |
299 | struct rsn_pmksa_cache_entry *entry = pmksa->pmksa; | |
300 | while (entry) { | |
301 | if ((aa == NULL || os_memcmp(entry->aa, aa, ETH_ALEN) == 0) && | |
302 | (pmkid == NULL || | |
303 | os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0)) | |
304 | return entry; | |
305 | entry = entry->next; | |
306 | } | |
307 | return NULL; | |
308 | } | |
309 | ||
310 | ||
311 | /** | |
312 | * pmksa_cache_notify_reconfig - Reconfiguration notification for PMKSA cache | |
313 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
314 | * | |
315 | * Clear references to old data structures when wpa_supplicant is reconfigured. | |
316 | */ | |
317 | void pmksa_cache_notify_reconfig(struct rsn_pmksa_cache *pmksa) | |
318 | { | |
319 | struct rsn_pmksa_cache_entry *entry = pmksa->pmksa; | |
320 | while (entry) { | |
321 | entry->network_ctx = NULL; | |
322 | entry = entry->next; | |
323 | } | |
324 | } | |
325 | ||
326 | ||
327 | static struct rsn_pmksa_cache_entry * | |
328 | pmksa_cache_clone_entry(struct rsn_pmksa_cache *pmksa, | |
329 | const struct rsn_pmksa_cache_entry *old_entry, | |
330 | const u8 *aa) | |
331 | { | |
332 | struct rsn_pmksa_cache_entry *new_entry; | |
333 | ||
334 | new_entry = pmksa_cache_add(pmksa, old_entry->pmk, old_entry->pmk_len, | |
335 | aa, pmksa->sm->own_addr, | |
56586197 | 336 | old_entry->network_ctx, old_entry->akmp); |
6fc6879b JM |
337 | if (new_entry == NULL) |
338 | return NULL; | |
339 | ||
340 | /* TODO: reorder entries based on expiration time? */ | |
341 | new_entry->expiration = old_entry->expiration; | |
342 | new_entry->opportunistic = 1; | |
343 | ||
344 | return new_entry; | |
345 | } | |
346 | ||
347 | ||
348 | /** | |
349 | * pmksa_cache_get_opportunistic - Try to get an opportunistic PMKSA entry | |
350 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
351 | * @network_ctx: Network configuration context | |
352 | * @aa: Authenticator address for the new AP | |
353 | * Returns: Pointer to a new PMKSA cache entry or %NULL if not available | |
354 | * | |
355 | * Try to create a new PMKSA cache entry opportunistically by guessing that the | |
356 | * new AP is sharing the same PMK as another AP that has the same SSID and has | |
357 | * already an entry in PMKSA cache. | |
358 | */ | |
359 | struct rsn_pmksa_cache_entry * | |
360 | pmksa_cache_get_opportunistic(struct rsn_pmksa_cache *pmksa, void *network_ctx, | |
361 | const u8 *aa) | |
362 | { | |
363 | struct rsn_pmksa_cache_entry *entry = pmksa->pmksa; | |
364 | ||
365 | if (network_ctx == NULL) | |
366 | return NULL; | |
367 | while (entry) { | |
368 | if (entry->network_ctx == network_ctx) { | |
369 | entry = pmksa_cache_clone_entry(pmksa, entry, aa); | |
370 | if (entry) { | |
371 | wpa_printf(MSG_DEBUG, "RSN: added " | |
372 | "opportunistic PMKSA cache entry " | |
373 | "for " MACSTR, MAC2STR(aa)); | |
374 | } | |
375 | return entry; | |
376 | } | |
377 | entry = entry->next; | |
378 | } | |
379 | return NULL; | |
380 | } | |
381 | ||
382 | ||
383 | /** | |
384 | * pmksa_cache_get_current - Get the current used PMKSA entry | |
385 | * @sm: Pointer to WPA state machine data from wpa_sm_init() | |
386 | * Returns: Pointer to the current PMKSA cache entry or %NULL if not available | |
387 | */ | |
388 | struct rsn_pmksa_cache_entry * pmksa_cache_get_current(struct wpa_sm *sm) | |
389 | { | |
390 | if (sm == NULL) | |
391 | return NULL; | |
392 | return sm->cur_pmksa; | |
393 | } | |
394 | ||
395 | ||
396 | /** | |
397 | * pmksa_cache_clear_current - Clear the current PMKSA entry selection | |
398 | * @sm: Pointer to WPA state machine data from wpa_sm_init() | |
399 | */ | |
400 | void pmksa_cache_clear_current(struct wpa_sm *sm) | |
401 | { | |
402 | if (sm == NULL) | |
403 | return; | |
404 | sm->cur_pmksa = NULL; | |
405 | } | |
406 | ||
407 | ||
408 | /** | |
409 | * pmksa_cache_set_current - Set the current PMKSA entry selection | |
410 | * @sm: Pointer to WPA state machine data from wpa_sm_init() | |
411 | * @pmkid: PMKID for selecting PMKSA or %NULL if not used | |
412 | * @bssid: BSSID for PMKSA or %NULL if not used | |
413 | * @network_ctx: Network configuration context | |
414 | * @try_opportunistic: Whether to allow opportunistic PMKSA caching | |
415 | * Returns: 0 if PMKSA was found or -1 if no matching entry was found | |
416 | */ | |
417 | int pmksa_cache_set_current(struct wpa_sm *sm, const u8 *pmkid, | |
418 | const u8 *bssid, void *network_ctx, | |
419 | int try_opportunistic) | |
420 | { | |
421 | struct rsn_pmksa_cache *pmksa = sm->pmksa; | |
422 | sm->cur_pmksa = NULL; | |
423 | if (pmkid) | |
424 | sm->cur_pmksa = pmksa_cache_get(pmksa, NULL, pmkid); | |
425 | if (sm->cur_pmksa == NULL && bssid) | |
426 | sm->cur_pmksa = pmksa_cache_get(pmksa, bssid, NULL); | |
427 | if (sm->cur_pmksa == NULL && try_opportunistic && bssid) | |
428 | sm->cur_pmksa = pmksa_cache_get_opportunistic(pmksa, | |
429 | network_ctx, | |
430 | bssid); | |
431 | if (sm->cur_pmksa) { | |
432 | wpa_hexdump(MSG_DEBUG, "RSN: PMKID", | |
433 | sm->cur_pmksa->pmkid, PMKID_LEN); | |
434 | return 0; | |
435 | } | |
436 | return -1; | |
437 | } | |
438 | ||
439 | ||
440 | /** | |
441 | * pmksa_cache_list - Dump text list of entries in PMKSA cache | |
442 | * @sm: Pointer to WPA state machine data from wpa_sm_init() | |
443 | * @buf: Buffer for the list | |
444 | * @len: Length of the buffer | |
445 | * Returns: number of bytes written to buffer | |
446 | * | |
447 | * This function is used to generate a text format representation of the | |
448 | * current PMKSA cache contents for the ctrl_iface PMKSA command. | |
449 | */ | |
450 | int pmksa_cache_list(struct wpa_sm *sm, char *buf, size_t len) | |
451 | { | |
452 | int i, ret; | |
453 | char *pos = buf; | |
454 | struct rsn_pmksa_cache_entry *entry; | |
455 | struct os_time now; | |
456 | ||
457 | os_get_time(&now); | |
458 | ret = os_snprintf(pos, buf + len - pos, | |
459 | "Index / AA / PMKID / expiration (in seconds) / " | |
460 | "opportunistic\n"); | |
461 | if (ret < 0 || ret >= buf + len - pos) | |
462 | return pos - buf; | |
463 | pos += ret; | |
464 | i = 0; | |
465 | entry = sm->pmksa->pmksa; | |
466 | while (entry) { | |
467 | i++; | |
468 | ret = os_snprintf(pos, buf + len - pos, "%d " MACSTR " ", | |
469 | i, MAC2STR(entry->aa)); | |
470 | if (ret < 0 || ret >= buf + len - pos) | |
471 | return pos - buf; | |
472 | pos += ret; | |
473 | pos += wpa_snprintf_hex(pos, buf + len - pos, entry->pmkid, | |
474 | PMKID_LEN); | |
475 | ret = os_snprintf(pos, buf + len - pos, " %d %d\n", | |
476 | (int) (entry->expiration - now.sec), | |
477 | entry->opportunistic); | |
478 | if (ret < 0 || ret >= buf + len - pos) | |
479 | return pos - buf; | |
480 | pos += ret; | |
481 | entry = entry->next; | |
482 | } | |
483 | return pos - buf; | |
484 | } | |
485 | ||
486 | ||
487 | /** | |
488 | * pmksa_cache_init - Initialize PMKSA cache | |
489 | * @free_cb: Callback function to be called when a PMKSA cache entry is freed | |
490 | * @ctx: Context pointer for free_cb function | |
491 | * @sm: Pointer to WPA state machine data from wpa_sm_init() | |
492 | * Returns: Pointer to PMKSA cache data or %NULL on failure | |
493 | */ | |
494 | struct rsn_pmksa_cache * | |
495 | pmksa_cache_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry, | |
496 | void *ctx, int replace), | |
497 | void *ctx, struct wpa_sm *sm) | |
498 | { | |
499 | struct rsn_pmksa_cache *pmksa; | |
500 | ||
501 | pmksa = os_zalloc(sizeof(*pmksa)); | |
502 | if (pmksa) { | |
503 | pmksa->free_cb = free_cb; | |
504 | pmksa->ctx = ctx; | |
505 | pmksa->sm = sm; | |
506 | } | |
507 | ||
508 | return pmksa; | |
509 | } | |
510 | ||
511 | #endif /* IEEE8021X_EAPOL and !CONFIG_NO_WPA2 */ |