1 /*
2 * RSN PTKSA cache implementation
3 *
4 * Copyright (C) 2019 Intel Corporation
5 *
6 * This software may be distributed under the terms of the BSD license.
7 * See README for more details.
8 */
9
10 #include "includes.h"
11 #include "utils/common.h"
12 #include "eloop.h"
13 #include "common/ptksa_cache.h"
14
15 #define PTKSA_CACHE_MAX_ENTRIES 16
16
17 struct ptksa_cache {
18 struct dl_list ptksa;
19 unsigned int n_ptksa;
20 };
21
22 #ifdef CONFIG_PTKSA_CACHE
23
24 static void ptksa_cache_set_expiration(struct ptksa_cache *ptksa);
25
26
ptksa_cache_free_entry(struct ptksa_cache * ptksa,struct ptksa_cache_entry * entry)27 static void ptksa_cache_free_entry(struct ptksa_cache *ptksa,
28 struct ptksa_cache_entry *entry)
29 {
30 ptksa->n_ptksa--;
31
32 dl_list_del(&entry->list);
33 bin_clear_free(entry, sizeof(*entry));
34 }
35
36
ptksa_cache_expire(void * eloop_ctx,void * timeout_ctx)37 static void ptksa_cache_expire(void *eloop_ctx, void *timeout_ctx)
38 {
39 struct ptksa_cache *ptksa = eloop_ctx;
40 struct ptksa_cache_entry *e, *next;
41 struct os_reltime now;
42 struct dl_list expired;
43
44 if (!ptksa)
45 return;
46
47 os_get_reltime(&now);
48 dl_list_init(&expired);
49
50 /*
51 * Move expired entries from the main ptksa list to a temporary
52 * 'expired' list. This prevents issues if the callback (e->cb)
53 * triggers operations like ptksa_cache_flush(), which would iterate
54 * over ptksa->ptksa. By removing entries first, flush operations
55 * will not double-process or double-free these entries.
56 */
57 dl_list_for_each_safe(e, next, &ptksa->ptksa,
58 struct ptksa_cache_entry, list) {
59 if (e->expiration > now.sec)
60 continue;
61
62 wpa_printf(MSG_DEBUG, "Expired PTKSA cache entry for " MACSTR,
63 MAC2STR(e->addr));
64
65 dl_list_del(&e->list);
66 ptksa->n_ptksa--;
67 dl_list_add_tail(&expired, &e->list);
68 }
69
70 dl_list_for_each_safe(e, next, &expired,
71 struct ptksa_cache_entry, list) {
72 dl_list_del(&e->list);
73 if (e->cb && e->ctx)
74 e->cb(e);
75 bin_clear_free(e, sizeof(*e));
76 }
77
78 ptksa_cache_set_expiration(ptksa);
79 }
80
81
ptksa_cache_set_expiration(struct ptksa_cache * ptksa)82 static void ptksa_cache_set_expiration(struct ptksa_cache *ptksa)
83 {
84 struct ptksa_cache_entry *e;
85 int sec;
86 struct os_reltime now;
87
88 eloop_cancel_timeout(ptksa_cache_expire, ptksa, NULL);
89
90 if (!ptksa || !ptksa->n_ptksa)
91 return;
92
93 e = dl_list_first(&ptksa->ptksa, struct ptksa_cache_entry, list);
94 if (!e)
95 return;
96
97 os_get_reltime(&now);
98 sec = e->expiration - now.sec;
99 if (sec < 0)
100 sec = 0;
101
102 eloop_register_timeout(sec + 1, 0, ptksa_cache_expire, ptksa, NULL);
103 }
104
105
106 /*
107 * ptksa_cache_init - Initialize PTKSA cache
108 *
109 * Returns: Pointer to PTKSA cache data or %NULL on failure
110 */
ptksa_cache_init(void)111 struct ptksa_cache * ptksa_cache_init(void)
112 {
113 struct ptksa_cache *ptksa = os_zalloc(sizeof(struct ptksa_cache));
114
115 wpa_printf(MSG_DEBUG, "PTKSA: Initializing");
116
117 if (ptksa)
118 dl_list_init(&ptksa->ptksa);
119
120 return ptksa;
121 }
122
123
124 /*
125 * ptksa_cache_deinit - Free all entries in PTKSA cache
126 * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
127 */
ptksa_cache_deinit(struct ptksa_cache * ptksa)128 void ptksa_cache_deinit(struct ptksa_cache *ptksa)
129 {
130 struct ptksa_cache_entry *e, *next;
131
132 if (!ptksa)
133 return;
134
135 wpa_printf(MSG_DEBUG, "PTKSA: Deinit. n_ptksa=%u", ptksa->n_ptksa);
136
137 dl_list_for_each_safe(e, next, &ptksa->ptksa,
138 struct ptksa_cache_entry, list)
139 ptksa_cache_free_entry(ptksa, e);
140
141 eloop_cancel_timeout(ptksa_cache_expire, ptksa, NULL);
142 os_free(ptksa);
143 }
144
145
146 /*
147 * ptksa_cache_get - Fetch a PTKSA cache entry
148 * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
149 * @addr: Peer address or %NULL to match any
150 * @cipher: Specific cipher suite to search for or WPA_CIPHER_NONE for any
151 * Returns: Pointer to PTKSA cache entry or %NULL if no match was found
152 */
ptksa_cache_get(struct ptksa_cache * ptksa,const u8 * addr,u32 cipher)153 struct ptksa_cache_entry * ptksa_cache_get(struct ptksa_cache *ptksa,
154 const u8 *addr, u32 cipher)
155 {
156 struct ptksa_cache_entry *e;
157
158 if (!ptksa)
159 return NULL;
160
161 dl_list_for_each(e, &ptksa->ptksa, struct ptksa_cache_entry, list) {
162 if ((!addr || ether_addr_equal(e->addr, addr)) &&
163 (cipher == WPA_CIPHER_NONE || cipher == e->cipher))
164 return e;
165 }
166
167 return NULL;
168 }
169
170
171 /*
172 * ptksa_cache_list - Dump text list of entries in PTKSA cache
173 * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
174 * @buf: Buffer for the list
175 * @len: Length of the buffer
176 * Returns: Number of bytes written to buffer
177 *
178 * This function is used to generate a text format representation of the
179 * current PTKSA cache contents for the ctrl_iface PTKSA command.
180 */
ptksa_cache_list(struct ptksa_cache * ptksa,char * buf,size_t len)181 int ptksa_cache_list(struct ptksa_cache *ptksa, char *buf, size_t len)
182 {
183 struct ptksa_cache_entry *e;
184 int i = 0, ret;
185 char *pos = buf;
186 struct os_reltime now;
187
188 if (!ptksa)
189 return 0;
190
191 os_get_reltime(&now);
192
193 ret = os_snprintf(pos, buf + len - pos,
194 "Index / ADDR / Cipher / expiration (secs) / TK / KDK\n");
195 if (os_snprintf_error(buf + len - pos, ret))
196 return pos - buf;
197 pos += ret;
198
199 dl_list_for_each(e, &ptksa->ptksa, struct ptksa_cache_entry, list) {
200 ret = os_snprintf(pos, buf + len - pos, "%u " MACSTR,
201 i, MAC2STR(e->addr));
202 if (os_snprintf_error(buf + len - pos, ret))
203 return pos - buf;
204 pos += ret;
205
206 ret = os_snprintf(pos, buf + len - pos, " %s %lu ",
207 wpa_cipher_txt(e->cipher),
208 e->expiration - now.sec);
209 if (os_snprintf_error(buf + len - pos, ret))
210 return pos - buf;
211 pos += ret;
212
213 ret = wpa_snprintf_hex(pos, buf + len - pos, e->ptk.tk,
214 e->ptk.tk_len);
215 if (os_snprintf_error(buf + len - pos, ret))
216 return pos - buf;
217 pos += ret;
218
219 ret = os_snprintf(pos, buf + len - pos, " ");
220 if (os_snprintf_error(buf + len - pos, ret))
221 return pos - buf;
222 pos += ret;
223
224 ret = wpa_snprintf_hex(pos, buf + len - pos, e->ptk.kdk,
225 e->ptk.kdk_len);
226 if (os_snprintf_error(buf + len - pos, ret))
227 return pos - buf;
228 pos += ret;
229
230 ret = os_snprintf(pos, buf + len - pos, "\n");
231 if (os_snprintf_error(buf + len - pos, ret))
232 return pos - buf;
233 pos += ret;
234
235 i++;
236 }
237
238 return pos - buf;
239 }
240
241
242 /*
243 * ptksa_cache_flush - Flush PTKSA cache entries
244 *
245 * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
246 * @addr: Peer address or %NULL to match any
247 * @cipher: Specific cipher suite to search for or WPA_CIPHER_NONE for any
248 */
ptksa_cache_flush(struct ptksa_cache * ptksa,const u8 * addr,u32 cipher)249 void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
250 {
251 struct ptksa_cache_entry *e, *next;
252 bool removed = false;
253
254 if (!ptksa)
255 return;
256
257 dl_list_for_each_safe(e, next, &ptksa->ptksa, struct ptksa_cache_entry,
258 list) {
259 if ((!addr || ether_addr_equal(e->addr, addr)) &&
260 (cipher == WPA_CIPHER_NONE || cipher == e->cipher)) {
261 wpa_printf(MSG_DEBUG,
262 "Flush PTKSA cache entry for " MACSTR,
263 MAC2STR(e->addr));
264
265 ptksa_cache_free_entry(ptksa, e);
266 removed = true;
267 }
268 }
269
270 if (removed)
271 ptksa_cache_set_expiration(ptksa);
272 }
273
274
275 /*
276 * ptksa_cache_add - Add a PTKSA cache entry
277 * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
278 * @own_addr: Own MAC address
279 * @addr: Peer address
280 * @cipher: The cipher used
281 * @life_time: The PTK life time in seconds
282 * @ptk: The PTK
283 * @life_time_expiry_cb: Callback for alternative expiration handling
284 * @ctx: Context pointer to save into e->ctx for the callback
285 * @akmp: The key management mechanism that was used to derive the PTK
286 * @auth_alg: The authentication algorithm that was used to derive the PTK
287 * Returns: Pointer to the added PTKSA cache entry or %NULL on error
288 *
289 * This function creates a PTKSA entry and adds it to the PTKSA cache.
290 * If an old entry is already in the cache for the same peer and cipher
291 * this entry will be replaced with the new entry.
292 */
ptksa_cache_add(struct ptksa_cache * ptksa,const u8 * own_addr,const u8 * addr,u32 cipher,u32 life_time,const struct wpa_ptk * ptk,void (* life_time_expiry_cb)(struct ptksa_cache_entry * e),void * ctx,u32 akmp,u16 auth_alg)293 struct ptksa_cache_entry * ptksa_cache_add(struct ptksa_cache *ptksa,
294 const u8 *own_addr,
295 const u8 *addr, u32 cipher,
296 u32 life_time,
297 const struct wpa_ptk *ptk,
298 void (*life_time_expiry_cb)
299 (struct ptksa_cache_entry *e),
300 void *ctx, u32 akmp, u16 auth_alg)
301 {
302 struct ptksa_cache_entry *entry, *tmp, *tmp2 = NULL;
303 struct os_reltime now;
304 bool set_expiry = false;
305
306 if (!ptksa || !ptk || !addr || !life_time || cipher == WPA_CIPHER_NONE)
307 return NULL;
308
309 /* remove a previous entry if present */
310 ptksa_cache_flush(ptksa, addr, cipher);
311
312 /* no place to add another entry */
313 if (ptksa->n_ptksa >= PTKSA_CACHE_MAX_ENTRIES)
314 return NULL;
315
316 entry = os_zalloc(sizeof(*entry));
317 if (!entry)
318 return NULL;
319
320 dl_list_init(&entry->list);
321 os_memcpy(entry->addr, addr, ETH_ALEN);
322 entry->cipher = cipher;
323 entry->cb = life_time_expiry_cb;
324 entry->ctx = ctx;
325 entry->akmp = akmp;
326 entry->auth_alg = auth_alg;
327
328 if (own_addr)
329 os_memcpy(entry->own_addr, own_addr, ETH_ALEN);
330
331 os_memcpy(&entry->ptk, ptk, sizeof(entry->ptk));
332
333 os_get_reltime(&now);
334 entry->expiration = now.sec + life_time;
335
336 dl_list_for_each(tmp, &ptksa->ptksa, struct ptksa_cache_entry, list) {
337 if (tmp->expiration > entry->expiration) {
338 tmp2 = tmp;
339 break;
340 }
341 }
342
343 if (dl_list_empty(&entry->list))
344 set_expiry = true;
345 /*
346 * If the expiration is later then all other or the list is empty
347 * entries, add it to the end of the list;
348 * otherwise add it before the relevant entry.
349 */
350 if (tmp2)
351 dl_list_add(&tmp2->list, &entry->list);
352 else
353 dl_list_add_tail(&ptksa->ptksa, &entry->list);
354
355 ptksa->n_ptksa++;
356 wpa_printf(MSG_DEBUG,
357 "Added PTKSA cache entry addr=" MACSTR " cipher=%u",
358 MAC2STR(addr), cipher);
359
360 if (set_expiry)
361 ptksa_cache_set_expiration(ptksa);
362
363 return entry;
364 }
365
366 #else /* CONFIG_PTKSA_CACHE */
367
ptksa_cache_init(void)368 struct ptksa_cache * ptksa_cache_init(void)
369 {
370 return (struct ptksa_cache *) 1;
371 }
372
373
ptksa_cache_deinit(struct ptksa_cache * ptksa)374 void ptksa_cache_deinit(struct ptksa_cache *ptksa)
375 {
376 }
377
378
379 struct ptksa_cache_entry *
ptksa_cache_get(struct ptksa_cache * ptksa,const u8 * addr,u32 cipher)380 ptksa_cache_get(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
381 {
382 return NULL;
383 }
384
385
ptksa_cache_list(struct ptksa_cache * ptksa,char * buf,size_t len)386 int ptksa_cache_list(struct ptksa_cache *ptksa, char *buf, size_t len)
387 {
388 return -1;
389 }
390
391
392 struct ptksa_cache_entry *
ptksa_cache_add(struct ptksa_cache * ptksa,const u8 * own_addr,const u8 * addr,u32 cipher,u32 life_time,const struct wpa_ptk * ptk,void (* cb)(struct ptksa_cache_entry * e),void * ctx,u32 akmp,u16 auth_alg)393 ptksa_cache_add(struct ptksa_cache *ptksa, const u8 *own_addr, const u8 *addr,
394 u32 cipher, u32 life_time, const struct wpa_ptk *ptk,
395 void (*cb)(struct ptksa_cache_entry *e), void *ctx, u32 akmp,
396 u16 auth_alg)
397 {
398 return NULL;
399 }
400
401
ptksa_cache_flush(struct ptksa_cache * ptksa,const u8 * addr,u32 cipher)402 void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
403 {
404 }
405
406 #endif /* CONFIG_PTKSA_CACHE */
407