xref: /freebsd/contrib/wpa/src/common/ptksa_cache.c (revision 71e72c9e91c4b8007a4292e09669e8b549c29e97)
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