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; 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