]>
Commit | Line | Data |
---|---|---|
6fc6879b JM |
1 | /* |
2 | * hostapd - PMKSA cache for IEEE 802.11i RSN | |
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 "ap.h" | |
19 | #include "config.h" | |
20 | #include "common.h" | |
21 | #include "eloop.h" | |
22 | #include "sha1.h" | |
56586197 | 23 | #include "sha256.h" |
6fc6879b JM |
24 | #include "ieee802_1x.h" |
25 | #include "eapol_sm.h" | |
26 | #include "pmksa_cache.h" | |
27 | ||
28 | ||
29 | static const int pmksa_cache_max_entries = 1024; | |
30 | static const int dot11RSNAConfigPMKLifetime = 43200; | |
31 | ||
32 | struct rsn_pmksa_cache { | |
33 | #define PMKID_HASH_SIZE 128 | |
34 | #define PMKID_HASH(pmkid) (unsigned int) ((pmkid)[0] & 0x7f) | |
35 | struct rsn_pmksa_cache_entry *pmkid[PMKID_HASH_SIZE]; | |
36 | struct rsn_pmksa_cache_entry *pmksa; | |
37 | int pmksa_count; | |
38 | ||
39 | void (*free_cb)(struct rsn_pmksa_cache_entry *entry, void *ctx); | |
40 | void *ctx; | |
41 | }; | |
42 | ||
43 | ||
44 | /** | |
45 | * rsn_pmkid - Calculate PMK identifier | |
46 | * @pmk: Pairwise master key | |
47 | * @pmk_len: Length of pmk in bytes | |
48 | * @aa: Authenticator address | |
49 | * @spa: Supplicant address | |
56586197 | 50 | * @use_sha256: Whether to use SHA256-based KDF |
6fc6879b JM |
51 | * |
52 | * IEEE Std 802.11i-2004 - 8.5.1.2 Pairwise key hierarchy | |
53 | * PMKID = HMAC-SHA1-128(PMK, "PMK Name" || AA || SPA) | |
54 | */ | |
55 | void rsn_pmkid(const u8 *pmk, size_t pmk_len, const u8 *aa, const u8 *spa, | |
56586197 | 56 | u8 *pmkid, int use_sha256) |
6fc6879b JM |
57 | { |
58 | char *title = "PMK Name"; | |
59 | const u8 *addr[3]; | |
60 | const size_t len[3] = { 8, ETH_ALEN, ETH_ALEN }; | |
56586197 | 61 | unsigned char hash[SHA256_MAC_LEN]; |
6fc6879b JM |
62 | |
63 | addr[0] = (u8 *) title; | |
64 | addr[1] = aa; | |
65 | addr[2] = spa; | |
66 | ||
56586197 JM |
67 | #ifdef CONFIG_IEEE80211W |
68 | if (use_sha256) | |
69 | hmac_sha256_vector(pmk, pmk_len, 3, addr, len, hash); | |
70 | else | |
71 | #endif /* CONFIG_IEEE80211W */ | |
72 | hmac_sha1_vector(pmk, pmk_len, 3, addr, len, hash); | |
6fc6879b JM |
73 | os_memcpy(pmkid, hash, PMKID_LEN); |
74 | } | |
75 | ||
76 | ||
77 | static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa); | |
78 | ||
79 | ||
80 | static void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry) | |
81 | { | |
82 | if (entry == NULL) | |
83 | return; | |
84 | os_free(entry->identity); | |
85 | ieee802_1x_free_radius_class(&entry->radius_class); | |
86 | os_free(entry); | |
87 | } | |
88 | ||
89 | ||
90 | static void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, | |
91 | struct rsn_pmksa_cache_entry *entry) | |
92 | { | |
93 | struct rsn_pmksa_cache_entry *pos, *prev; | |
94 | ||
95 | pmksa->pmksa_count--; | |
96 | pmksa->free_cb(entry, pmksa->ctx); | |
97 | pos = pmksa->pmkid[PMKID_HASH(entry->pmkid)]; | |
98 | prev = NULL; | |
99 | while (pos) { | |
100 | if (pos == entry) { | |
101 | if (prev != NULL) { | |
102 | prev->hnext = pos->hnext; | |
103 | } else { | |
104 | pmksa->pmkid[PMKID_HASH(entry->pmkid)] = | |
105 | pos->hnext; | |
106 | } | |
107 | break; | |
108 | } | |
109 | prev = pos; | |
110 | pos = pos->hnext; | |
111 | } | |
112 | ||
113 | pos = pmksa->pmksa; | |
114 | prev = NULL; | |
115 | while (pos) { | |
116 | if (pos == entry) { | |
117 | if (prev != NULL) | |
118 | prev->next = pos->next; | |
119 | else | |
120 | pmksa->pmksa = pos->next; | |
121 | break; | |
122 | } | |
123 | prev = pos; | |
124 | pos = pos->next; | |
125 | } | |
126 | _pmksa_cache_free_entry(entry); | |
127 | } | |
128 | ||
129 | ||
130 | static void pmksa_cache_expire(void *eloop_ctx, void *timeout_ctx) | |
131 | { | |
132 | struct rsn_pmksa_cache *pmksa = eloop_ctx; | |
133 | struct os_time now; | |
134 | ||
135 | os_get_time(&now); | |
136 | while (pmksa->pmksa && pmksa->pmksa->expiration <= now.sec) { | |
137 | struct rsn_pmksa_cache_entry *entry = pmksa->pmksa; | |
138 | pmksa->pmksa = entry->next; | |
139 | wpa_printf(MSG_DEBUG, "RSN: expired PMKSA cache entry for " | |
140 | MACSTR, MAC2STR(entry->spa)); | |
141 | pmksa_cache_free_entry(pmksa, entry); | |
142 | } | |
143 | ||
144 | pmksa_cache_set_expiration(pmksa); | |
145 | } | |
146 | ||
147 | ||
148 | static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa) | |
149 | { | |
150 | int sec; | |
151 | struct os_time now; | |
152 | ||
153 | eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL); | |
154 | if (pmksa->pmksa == NULL) | |
155 | return; | |
156 | os_get_time(&now); | |
157 | sec = pmksa->pmksa->expiration - now.sec; | |
158 | if (sec < 0) | |
159 | sec = 0; | |
160 | eloop_register_timeout(sec + 1, 0, pmksa_cache_expire, pmksa, NULL); | |
161 | } | |
162 | ||
163 | ||
164 | static void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache_entry *entry, | |
165 | struct eapol_state_machine *eapol) | |
166 | { | |
167 | if (eapol == NULL) | |
168 | return; | |
169 | ||
170 | if (eapol->identity) { | |
171 | entry->identity = os_malloc(eapol->identity_len); | |
172 | if (entry->identity) { | |
173 | entry->identity_len = eapol->identity_len; | |
174 | os_memcpy(entry->identity, eapol->identity, | |
175 | eapol->identity_len); | |
176 | } | |
177 | } | |
178 | ||
179 | ieee802_1x_copy_radius_class(&entry->radius_class, | |
180 | &eapol->radius_class); | |
181 | ||
182 | entry->eap_type_authsrv = eapol->eap_type_authsrv; | |
183 | entry->vlan_id = eapol->sta->vlan_id; | |
184 | } | |
185 | ||
186 | ||
187 | void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache_entry *entry, | |
188 | struct eapol_state_machine *eapol) | |
189 | { | |
190 | if (entry == NULL || eapol == NULL) | |
191 | return; | |
192 | ||
193 | if (entry->identity) { | |
194 | os_free(eapol->identity); | |
195 | eapol->identity = os_malloc(entry->identity_len); | |
196 | if (eapol->identity) { | |
197 | eapol->identity_len = entry->identity_len; | |
198 | os_memcpy(eapol->identity, entry->identity, | |
199 | entry->identity_len); | |
200 | } | |
201 | wpa_hexdump_ascii(MSG_DEBUG, "STA identity from PMKSA", | |
202 | eapol->identity, eapol->identity_len); | |
203 | } | |
204 | ||
205 | ieee802_1x_free_radius_class(&eapol->radius_class); | |
206 | ieee802_1x_copy_radius_class(&eapol->radius_class, | |
207 | &entry->radius_class); | |
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; | |
214 | eapol->sta->vlan_id = entry->vlan_id; | |
215 | } | |
216 | ||
217 | ||
bf98f7f3 JM |
218 | static void pmksa_cache_link_entry(struct rsn_pmksa_cache *pmksa, |
219 | struct rsn_pmksa_cache_entry *entry) | |
220 | { | |
221 | struct rsn_pmksa_cache_entry *pos, *prev; | |
222 | ||
223 | /* Add the new entry; order by expiration time */ | |
224 | pos = pmksa->pmksa; | |
225 | prev = NULL; | |
226 | while (pos) { | |
227 | if (pos->expiration > entry->expiration) | |
228 | break; | |
229 | prev = pos; | |
230 | pos = pos->next; | |
231 | } | |
232 | if (prev == NULL) { | |
233 | entry->next = pmksa->pmksa; | |
234 | pmksa->pmksa = entry; | |
235 | } else { | |
236 | entry->next = prev->next; | |
237 | prev->next = entry; | |
238 | } | |
239 | entry->hnext = pmksa->pmkid[PMKID_HASH(entry->pmkid)]; | |
240 | pmksa->pmkid[PMKID_HASH(entry->pmkid)] = entry; | |
241 | ||
242 | pmksa->pmksa_count++; | |
243 | wpa_printf(MSG_DEBUG, "RSN: added PMKSA cache entry for " MACSTR, | |
244 | MAC2STR(entry->spa)); | |
245 | wpa_hexdump(MSG_DEBUG, "RSN: added PMKID", entry->pmkid, PMKID_LEN); | |
246 | } | |
247 | ||
248 | ||
6fc6879b JM |
249 | /** |
250 | * pmksa_cache_add - Add a PMKSA cache entry | |
251 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
252 | * @pmk: The new pairwise master key | |
253 | * @pmk_len: PMK length in bytes, usually PMK_LEN (32) | |
254 | * @aa: Authenticator address | |
255 | * @spa: Supplicant address | |
256 | * @session_timeout: Session timeout | |
257 | * @eapol: Pointer to EAPOL state machine data | |
56586197 | 258 | * @akmp: WPA_KEY_MGMT_* used in key derivation |
6fc6879b JM |
259 | * Returns: Pointer to the added PMKSA cache entry or %NULL on error |
260 | * | |
261 | * This function create a PMKSA entry for a new PMK and adds it to the PMKSA | |
262 | * cache. If an old entry is already in the cache for the same Supplicant, | |
263 | * this entry will be replaced with the new entry. PMKID will be calculated | |
264 | * based on the PMK. | |
265 | */ | |
266 | struct rsn_pmksa_cache_entry * | |
267 | pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len, | |
268 | const u8 *aa, const u8 *spa, int session_timeout, | |
56586197 | 269 | struct eapol_state_machine *eapol, int akmp) |
6fc6879b | 270 | { |
bf98f7f3 | 271 | struct rsn_pmksa_cache_entry *entry, *pos; |
6fc6879b JM |
272 | struct os_time now; |
273 | ||
274 | if (pmk_len > PMK_LEN) | |
275 | return NULL; | |
276 | ||
277 | entry = os_zalloc(sizeof(*entry)); | |
278 | if (entry == NULL) | |
279 | return NULL; | |
280 | os_memcpy(entry->pmk, pmk, pmk_len); | |
281 | entry->pmk_len = pmk_len; | |
56586197 JM |
282 | rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, |
283 | wpa_key_mgmt_sha256(akmp)); | |
6fc6879b JM |
284 | os_get_time(&now); |
285 | entry->expiration = now.sec; | |
286 | if (session_timeout > 0) | |
287 | entry->expiration += session_timeout; | |
288 | else | |
289 | entry->expiration += dot11RSNAConfigPMKLifetime; | |
56586197 | 290 | entry->akmp = akmp; |
6fc6879b JM |
291 | os_memcpy(entry->spa, spa, ETH_ALEN); |
292 | pmksa_cache_from_eapol_data(entry, eapol); | |
293 | ||
294 | /* Replace an old entry for the same STA (if found) with the new entry | |
295 | */ | |
296 | pos = pmksa_cache_get(pmksa, spa, NULL); | |
297 | if (pos) | |
298 | pmksa_cache_free_entry(pmksa, pos); | |
299 | ||
300 | if (pmksa->pmksa_count >= pmksa_cache_max_entries && pmksa->pmksa) { | |
301 | /* Remove the oldest entry to make room for the new entry */ | |
302 | wpa_printf(MSG_DEBUG, "RSN: removed the oldest PMKSA cache " | |
303 | "entry (for " MACSTR ") to make room for new one", | |
304 | MAC2STR(pmksa->pmksa->spa)); | |
305 | pmksa_cache_free_entry(pmksa, pmksa->pmksa); | |
306 | } | |
307 | ||
bf98f7f3 JM |
308 | pmksa_cache_link_entry(pmksa, entry); |
309 | ||
310 | return entry; | |
311 | } | |
312 | ||
313 | ||
314 | struct rsn_pmksa_cache_entry * | |
315 | pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa, | |
316 | const struct rsn_pmksa_cache_entry *old_entry, | |
317 | const u8 *aa, const u8 *pmkid) | |
318 | { | |
319 | struct rsn_pmksa_cache_entry *entry; | |
320 | ||
321 | entry = os_zalloc(sizeof(*entry)); | |
322 | if (entry == NULL) | |
323 | return NULL; | |
324 | os_memcpy(entry->pmkid, pmkid, PMKID_LEN); | |
325 | os_memcpy(entry->pmk, old_entry->pmk, old_entry->pmk_len); | |
326 | entry->pmk_len = old_entry->pmk_len; | |
327 | entry->expiration = old_entry->expiration; | |
328 | entry->akmp = old_entry->akmp; | |
329 | os_memcpy(entry->spa, old_entry->spa, ETH_ALEN); | |
330 | entry->opportunistic = 1; | |
331 | if (old_entry->identity) { | |
332 | entry->identity = os_malloc(old_entry->identity_len); | |
333 | if (entry->identity) { | |
334 | entry->identity_len = old_entry->identity_len; | |
335 | os_memcpy(entry->identity, old_entry->identity, | |
336 | old_entry->identity_len); | |
337 | } | |
6fc6879b | 338 | } |
bf98f7f3 JM |
339 | ieee802_1x_copy_radius_class(&entry->radius_class, |
340 | &old_entry->radius_class); | |
341 | entry->eap_type_authsrv = old_entry->eap_type_authsrv; | |
342 | entry->vlan_id = old_entry->vlan_id; | |
343 | entry->opportunistic = 1; | |
6fc6879b | 344 | |
bf98f7f3 | 345 | pmksa_cache_link_entry(pmksa, entry); |
6fc6879b JM |
346 | |
347 | return entry; | |
348 | } | |
349 | ||
350 | ||
351 | /** | |
352 | * pmksa_cache_deinit - Free all entries in PMKSA cache | |
353 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
354 | */ | |
355 | void pmksa_cache_deinit(struct rsn_pmksa_cache *pmksa) | |
356 | { | |
357 | struct rsn_pmksa_cache_entry *entry, *prev; | |
358 | int i; | |
359 | ||
360 | if (pmksa == NULL) | |
361 | return; | |
362 | ||
363 | entry = pmksa->pmksa; | |
364 | while (entry) { | |
365 | prev = entry; | |
366 | entry = entry->next; | |
367 | _pmksa_cache_free_entry(prev); | |
368 | } | |
369 | eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL); | |
370 | for (i = 0; i < PMKID_HASH_SIZE; i++) | |
371 | pmksa->pmkid[i] = NULL; | |
372 | os_free(pmksa); | |
373 | } | |
374 | ||
375 | ||
376 | /** | |
377 | * pmksa_cache_get - Fetch a PMKSA cache entry | |
378 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
379 | * @spa: Supplicant address or %NULL to match any | |
380 | * @pmkid: PMKID or %NULL to match any | |
381 | * Returns: Pointer to PMKSA cache entry or %NULL if no match was found | |
382 | */ | |
383 | struct rsn_pmksa_cache_entry * pmksa_cache_get(struct rsn_pmksa_cache *pmksa, | |
384 | const u8 *spa, const u8 *pmkid) | |
385 | { | |
386 | struct rsn_pmksa_cache_entry *entry; | |
387 | ||
388 | if (pmkid) | |
389 | entry = pmksa->pmkid[PMKID_HASH(pmkid)]; | |
390 | else | |
391 | entry = pmksa->pmksa; | |
392 | while (entry) { | |
393 | if ((spa == NULL || | |
394 | os_memcmp(entry->spa, spa, ETH_ALEN) == 0) && | |
395 | (pmkid == NULL || | |
396 | os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0)) | |
397 | return entry; | |
398 | entry = pmkid ? entry->hnext : entry->next; | |
399 | } | |
400 | return NULL; | |
401 | } | |
402 | ||
403 | ||
bf98f7f3 JM |
404 | /** |
405 | * pmksa_cache_get_okc - Fetch a PMKSA cache entry using OKC | |
406 | * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init() | |
407 | * @spa: Supplicant address | |
408 | * @pmkid: PMKID | |
409 | * Returns: Pointer to PMKSA cache entry or %NULL if no match was found | |
410 | * | |
411 | * Use opportunistic key caching (OKC) to find a PMK for a supplicant. | |
412 | */ | |
413 | struct rsn_pmksa_cache_entry * pmksa_cache_get_okc( | |
414 | struct rsn_pmksa_cache *pmksa, const u8 *aa, const u8 *spa, | |
415 | const u8 *pmkid) | |
416 | { | |
417 | struct rsn_pmksa_cache_entry *entry; | |
418 | u8 new_pmkid[PMKID_LEN]; | |
419 | ||
420 | entry = pmksa->pmksa; | |
421 | while (entry) { | |
422 | if (os_memcmp(entry->spa, spa, ETH_ALEN) != 0) | |
423 | continue; | |
56586197 JM |
424 | rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa, new_pmkid, |
425 | wpa_key_mgmt_sha256(entry->akmp)); | |
bf98f7f3 JM |
426 | if (os_memcmp(new_pmkid, pmkid, PMKID_LEN) == 0) |
427 | return entry; | |
428 | entry = entry->next; | |
429 | } | |
430 | return NULL; | |
431 | } | |
432 | ||
433 | ||
6fc6879b JM |
434 | /** |
435 | * pmksa_cache_init - Initialize PMKSA cache | |
436 | * @free_cb: Callback function to be called when a PMKSA cache entry is freed | |
437 | * @ctx: Context pointer for free_cb function | |
438 | * Returns: Pointer to PMKSA cache data or %NULL on failure | |
439 | */ | |
440 | struct rsn_pmksa_cache * | |
441 | pmksa_cache_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry, | |
442 | void *ctx), void *ctx) | |
443 | { | |
444 | struct rsn_pmksa_cache *pmksa; | |
445 | ||
446 | pmksa = os_zalloc(sizeof(*pmksa)); | |
447 | if (pmksa) { | |
448 | pmksa->free_cb = free_cb; | |
449 | pmksa->ctx = ctx; | |
450 | } | |
451 | ||
452 | return pmksa; | |
453 | } |