xref: /freebsd/contrib/wpa/src/common/ptksa_cache.c (revision 2e3f49888ec8851bafb22011533217487764fdb0)
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 static void ptksa_cache_set_expiration(struct ptksa_cache *ptksa);
23 
24 
25 static void ptksa_cache_free_entry(struct ptksa_cache *ptksa,
26 				   struct ptksa_cache_entry *entry)
27 {
28 	ptksa->n_ptksa--;
29 
30 	dl_list_del(&entry->list);
31 	bin_clear_free(entry, sizeof(*entry));
32 }
33 
34 
35 static void ptksa_cache_expire(void *eloop_ctx, void *timeout_ctx)
36 {
37 	struct ptksa_cache *ptksa = eloop_ctx;
38 	struct ptksa_cache_entry *e, *next;
39 	struct os_reltime now;
40 
41 	if (!ptksa)
42 		return;
43 
44 	os_get_reltime(&now);
45 
46 	dl_list_for_each_safe(e, next, &ptksa->ptksa,
47 			      struct ptksa_cache_entry, list) {
48 		if (e->expiration > now.sec)
49 			continue;
50 
51 		wpa_printf(MSG_DEBUG, "Expired PTKSA cache entry for " MACSTR,
52 			   MAC2STR(e->addr));
53 
54 		ptksa_cache_free_entry(ptksa, e);
55 	}
56 
57 	ptksa_cache_set_expiration(ptksa);
58 }
59 
60 
61 static void ptksa_cache_set_expiration(struct ptksa_cache *ptksa)
62 {
63 	struct ptksa_cache_entry *e;
64 	int sec;
65 	struct os_reltime now;
66 
67 	eloop_cancel_timeout(ptksa_cache_expire, ptksa, NULL);
68 
69 	if (!ptksa || !ptksa->n_ptksa)
70 		return;
71 
72 	e = dl_list_first(&ptksa->ptksa, struct ptksa_cache_entry, list);
73 	if (!e)
74 		return;
75 
76 	os_get_reltime(&now);
77 	sec = e->expiration - now.sec;
78 	if (sec < 0)
79 		sec = 0;
80 
81 	eloop_register_timeout(sec + 1, 0, ptksa_cache_expire, ptksa, NULL);
82 }
83 
84 
85 /*
86  * ptksa_cache_init - Initialize PTKSA cache
87  *
88  * Returns: Pointer to PTKSA cache data or %NULL on failure
89  */
90 struct ptksa_cache * ptksa_cache_init(void)
91 {
92 	struct ptksa_cache *ptksa = os_zalloc(sizeof(struct ptksa_cache));
93 
94 	wpa_printf(MSG_DEBUG, "PTKSA: Initializing");
95 
96 	if (ptksa)
97 		dl_list_init(&ptksa->ptksa);
98 
99 	return ptksa;
100 }
101 
102 
103 /*
104  * ptksa_cache_deinit - Free all entries in PTKSA cache
105  * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
106  */
107 void ptksa_cache_deinit(struct ptksa_cache *ptksa)
108 {
109 	struct ptksa_cache_entry *e, *next;
110 
111 	if (!ptksa)
112 		return;
113 
114 	wpa_printf(MSG_DEBUG, "PTKSA: Deinit. n_ptksa=%u", ptksa->n_ptksa);
115 
116 	dl_list_for_each_safe(e, next, &ptksa->ptksa,
117 			      struct ptksa_cache_entry, list)
118 		ptksa_cache_free_entry(ptksa, e);
119 
120 	eloop_cancel_timeout(ptksa_cache_expire, ptksa, NULL);
121 	os_free(ptksa);
122 }
123 
124 
125 /*
126  * ptksa_cache_get - Fetch a PTKSA cache entry
127  * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
128  * @addr: Peer address or %NULL to match any
129  * @cipher: Specific cipher suite to search for or WPA_CIPHER_NONE for any
130  * Returns: Pointer to PTKSA cache entry or %NULL if no match was found
131  */
132 struct ptksa_cache_entry * ptksa_cache_get(struct ptksa_cache *ptksa,
133 					   const u8 *addr, u32 cipher)
134 {
135 	struct ptksa_cache_entry *e;
136 
137 	if (!ptksa)
138 		return NULL;
139 
140 	dl_list_for_each(e, &ptksa->ptksa, struct ptksa_cache_entry, list) {
141 		if ((!addr || os_memcmp(e->addr, addr, ETH_ALEN) == 0) &&
142 		    (cipher == WPA_CIPHER_NONE || cipher == e->cipher))
143 			return e;
144 	}
145 
146 	return NULL;
147 }
148 
149 
150 /*
151  * ptksa_cache_list - Dump text list of entries in PTKSA cache
152  * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
153  * @buf: Buffer for the list
154  * @len: Length of the buffer
155  * Returns: Number of bytes written to buffer
156  *
157  * This function is used to generate a text format representation of the
158  * current PTKSA cache contents for the ctrl_iface PTKSA command.
159  */
160 int ptksa_cache_list(struct ptksa_cache *ptksa, char *buf, size_t len)
161 {
162 	struct ptksa_cache_entry *e;
163 	int i = 0, ret;
164 	char *pos = buf;
165 	struct os_reltime now;
166 
167 	if (!ptksa)
168 		return 0;
169 
170 	os_get_reltime(&now);
171 
172 	ret = os_snprintf(pos, buf + len - pos,
173 			  "Index / ADDR / Cipher / expiration (secs) / TK / KDK\n");
174 	if (os_snprintf_error(buf + len - pos, ret))
175 		return pos - buf;
176 	pos += ret;
177 
178 	dl_list_for_each(e, &ptksa->ptksa, struct ptksa_cache_entry, list) {
179 		ret = os_snprintf(pos, buf + len - pos, "%u " MACSTR,
180 				  i, MAC2STR(e->addr));
181 		if (os_snprintf_error(buf + len - pos, ret))
182 			return pos - buf;
183 		pos += ret;
184 
185 		ret = os_snprintf(pos, buf + len - pos, " %s %lu ",
186 				  wpa_cipher_txt(e->cipher),
187 				  e->expiration - now.sec);
188 		if (os_snprintf_error(buf + len - pos, ret))
189 			return pos - buf;
190 		pos += ret;
191 
192 		ret = wpa_snprintf_hex(pos, buf + len - pos, e->ptk.tk,
193 				       e->ptk.tk_len);
194 		if (os_snprintf_error(buf + len - pos, ret))
195 			return pos - buf;
196 		pos += ret;
197 
198 		ret = os_snprintf(pos, buf + len - pos, " ");
199 		if (os_snprintf_error(buf + len - pos, ret))
200 			return pos - buf;
201 		pos += ret;
202 
203 		ret = wpa_snprintf_hex(pos, buf + len - pos, e->ptk.kdk,
204 				       e->ptk.kdk_len);
205 		if (os_snprintf_error(buf + len - pos, ret))
206 			return pos - buf;
207 		pos += ret;
208 
209 		ret = os_snprintf(pos, buf + len - pos, "\n");
210 		if (os_snprintf_error(buf + len - pos, ret))
211 			return pos - buf;
212 		pos += ret;
213 
214 		i++;
215 	}
216 
217 	return pos - buf;
218 }
219 
220 
221 /*
222  * ptksa_cache_flush - Flush PTKSA cache entries
223  *
224  * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
225  * @addr: Peer address or %NULL to match any
226  * @cipher: Specific cipher suite to search for or WPA_CIPHER_NONE for any
227  */
228 void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
229 {
230 	struct ptksa_cache_entry *e, *next;
231 	bool removed = false;
232 
233 	if (!ptksa)
234 		return;
235 
236 	dl_list_for_each_safe(e, next, &ptksa->ptksa, struct ptksa_cache_entry,
237 			      list) {
238 		if ((!addr || os_memcmp(e->addr, addr, ETH_ALEN) == 0) &&
239 		    (cipher == WPA_CIPHER_NONE || cipher == e->cipher)) {
240 			wpa_printf(MSG_DEBUG,
241 				   "Flush PTKSA cache entry for " MACSTR,
242 				   MAC2STR(e->addr));
243 
244 			ptksa_cache_free_entry(ptksa, e);
245 			removed = true;
246 		}
247 	}
248 
249 	if (removed)
250 		ptksa_cache_set_expiration(ptksa);
251 }
252 
253 
254 /*
255  * ptksa_cache_add - Add a PTKSA cache entry
256  * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init()
257  * @addr: Peer address
258  * @cipher: The cipher used
259  * @life_time: The PTK life time in seconds
260  * @ptk: The PTK
261  * Returns: Pointer to the added PTKSA cache entry or %NULL on error
262  *
263  * This function creates a PTKSA entry and adds it to the PTKSA cache.
264  * If an old entry is already in the cache for the same peer and cipher
265  * this entry will be replaced with the new entry.
266  */
267 struct ptksa_cache_entry * ptksa_cache_add(struct ptksa_cache *ptksa,
268 					   const u8 *addr, u32 cipher,
269 					   u32 life_time,
270 					   const struct wpa_ptk *ptk)
271 {
272 	struct ptksa_cache_entry *entry, *tmp, *tmp2 = NULL;
273 	struct os_reltime now;
274 
275 	if (!ptksa || !ptk || !addr || !life_time || cipher == WPA_CIPHER_NONE)
276 		return NULL;
277 
278 	/* remove a previous entry if present */
279 	ptksa_cache_flush(ptksa, addr, cipher);
280 
281 	/* no place to add another entry */
282 	if (ptksa->n_ptksa >= PTKSA_CACHE_MAX_ENTRIES)
283 		return NULL;
284 
285 	entry = os_zalloc(sizeof(*entry));
286 	if (!entry)
287 		return NULL;
288 
289 	dl_list_init(&entry->list);
290 	os_memcpy(entry->addr, addr, ETH_ALEN);
291 	entry->cipher = cipher;
292 
293 	os_memcpy(&entry->ptk, ptk, sizeof(entry->ptk));
294 
295 	os_get_reltime(&now);
296 	entry->expiration = now.sec + life_time;
297 
298 	dl_list_for_each(tmp, &ptksa->ptksa, struct ptksa_cache_entry, list) {
299 		if (tmp->expiration > entry->expiration) {
300 			tmp2 = tmp;
301 			break;
302 		}
303 	}
304 
305 	/*
306 	 * If the expiration is later then all other or the list is empty
307 	 * entries, add it to the end of the list;
308 	 * otherwise add it before the relevant entry.
309 	 */
310 	if (tmp2)
311 		dl_list_add(&tmp2->list, &entry->list);
312 	else
313 		dl_list_add_tail(&ptksa->ptksa, &entry->list);
314 
315 	ptksa->n_ptksa++;
316 	wpa_printf(MSG_DEBUG,
317 		   "Added PTKSA cache entry addr=" MACSTR " cipher=%u",
318 		   MAC2STR(addr), cipher);
319 
320 	return entry;
321 }
322