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