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