Home | History | Annotate | Line # | Download | only in common
      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 
     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 
     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 
     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  */
     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  */
    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  */
    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  */
    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  */
    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  */
    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 
    350 struct ptksa_cache * ptksa_cache_init(void)
    351 {
    352 	return (struct ptksa_cache *) 1;
    353 }
    354 
    355 
    356 void ptksa_cache_deinit(struct ptksa_cache *ptksa)
    357 {
    358 }
    359 
    360 
    361 struct ptksa_cache_entry *
    362 ptksa_cache_get(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
    363 {
    364 	return NULL;
    365 }
    366 
    367 
    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 *
    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 
    383 void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
    384 {
    385 }
    386 
    387 #endif /* CONFIG_PTKSA_CACHE */
    388