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