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 ptksa_cache_free_entry(ptksa, e);
55 }
56
57 ptksa_cache_set_expiration(ptksa);
58 }
59
60
ptksa_cache_set_expiration(struct ptksa_cache * ptksa)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 */
ptksa_cache_init(void)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 */
ptksa_cache_deinit(struct ptksa_cache * ptksa)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 */
ptksa_cache_get(struct ptksa_cache * ptksa,const u8 * addr,u32 cipher)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 */
ptksa_cache_list(struct ptksa_cache * ptksa,char * buf,size_t len)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 */
ptksa_cache_flush(struct ptksa_cache * ptksa,const u8 * addr,u32 cipher)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 */
ptksa_cache_add(struct ptksa_cache * ptksa,const u8 * addr,u32 cipher,u32 life_time,const struct wpa_ptk * ptk)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;
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 break;
301 }
302
303 /*
304 * If the list was empty add to the head; otherwise if the expiration is
305 * later then all other entries, add it to the end of the list;
306 * otherwise add it before the relevant entry.
307 */
308 if (!tmp)
309 dl_list_add(&ptksa->ptksa, &entry->list);
310 else if (tmp->expiration < entry->expiration)
311 dl_list_add(&tmp->list, &entry->list);
312 else
313 dl_list_add_tail(&tmp->list, &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