| /* |
| * RSN PTKSA cache implementation |
| * |
| * Copyright (C) 2019 Intel Corporation |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| #include "utils/common.h" |
| #include "eloop.h" |
| #include "common/ptksa_cache.h" |
| |
| #define PTKSA_CACHE_MAX_ENTRIES 16 |
| |
| struct ptksa_cache { |
| struct dl_list ptksa; |
| unsigned int n_ptksa; |
| }; |
| |
| static void ptksa_cache_set_expiration(struct ptksa_cache *ptksa); |
| |
| |
| static void ptksa_cache_free_entry(struct ptksa_cache *ptksa, |
| struct ptksa_cache_entry *entry) |
| { |
| ptksa->n_ptksa--; |
| |
| dl_list_del(&entry->list); |
| bin_clear_free(entry, sizeof(*entry)); |
| } |
| |
| |
| static void ptksa_cache_expire(void *eloop_ctx, void *timeout_ctx) |
| { |
| struct ptksa_cache *ptksa = eloop_ctx; |
| struct ptksa_cache_entry *e, *next; |
| struct os_reltime now; |
| |
| if (!ptksa) |
| return; |
| |
| os_get_reltime(&now); |
| |
| dl_list_for_each_safe(e, next, &ptksa->ptksa, |
| struct ptksa_cache_entry, list) { |
| if (e->expiration > now.sec) |
| continue; |
| |
| wpa_printf(MSG_DEBUG, "Expired PTKSA cache entry for " MACSTR, |
| MAC2STR(e->addr)); |
| |
| if (e->cb && e->ctx) |
| e->cb(e); |
| else |
| ptksa_cache_free_entry(ptksa, e); |
| } |
| |
| ptksa_cache_set_expiration(ptksa); |
| } |
| |
| |
| static void ptksa_cache_set_expiration(struct ptksa_cache *ptksa) |
| { |
| struct ptksa_cache_entry *e; |
| int sec; |
| struct os_reltime now; |
| |
| eloop_cancel_timeout(ptksa_cache_expire, ptksa, NULL); |
| |
| if (!ptksa || !ptksa->n_ptksa) |
| return; |
| |
| e = dl_list_first(&ptksa->ptksa, struct ptksa_cache_entry, list); |
| if (!e) |
| return; |
| |
| os_get_reltime(&now); |
| sec = e->expiration - now.sec; |
| if (sec < 0) |
| sec = 0; |
| |
| eloop_register_timeout(sec + 1, 0, ptksa_cache_expire, ptksa, NULL); |
| } |
| |
| |
| /* |
| * ptksa_cache_init - Initialize PTKSA cache |
| * |
| * Returns: Pointer to PTKSA cache data or %NULL on failure |
| */ |
| struct ptksa_cache * ptksa_cache_init(void) |
| { |
| struct ptksa_cache *ptksa = os_zalloc(sizeof(struct ptksa_cache)); |
| |
| wpa_printf(MSG_DEBUG, "PTKSA: Initializing"); |
| |
| if (ptksa) |
| dl_list_init(&ptksa->ptksa); |
| |
| return ptksa; |
| } |
| |
| |
| /* |
| * ptksa_cache_deinit - Free all entries in PTKSA cache |
| * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init() |
| */ |
| void ptksa_cache_deinit(struct ptksa_cache *ptksa) |
| { |
| struct ptksa_cache_entry *e, *next; |
| |
| if (!ptksa) |
| return; |
| |
| wpa_printf(MSG_DEBUG, "PTKSA: Deinit. n_ptksa=%u", ptksa->n_ptksa); |
| |
| dl_list_for_each_safe(e, next, &ptksa->ptksa, |
| struct ptksa_cache_entry, list) |
| ptksa_cache_free_entry(ptksa, e); |
| |
| eloop_cancel_timeout(ptksa_cache_expire, ptksa, NULL); |
| os_free(ptksa); |
| } |
| |
| |
| /* |
| * ptksa_cache_get - Fetch a PTKSA cache entry |
| * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init() |
| * @addr: Peer address or %NULL to match any |
| * @cipher: Specific cipher suite to search for or WPA_CIPHER_NONE for any |
| * Returns: Pointer to PTKSA cache entry or %NULL if no match was found |
| */ |
| struct ptksa_cache_entry * ptksa_cache_get(struct ptksa_cache *ptksa, |
| const u8 *addr, u32 cipher) |
| { |
| struct ptksa_cache_entry *e; |
| |
| if (!ptksa) |
| return NULL; |
| |
| dl_list_for_each(e, &ptksa->ptksa, struct ptksa_cache_entry, list) { |
| if ((!addr || os_memcmp(e->addr, addr, ETH_ALEN) == 0) && |
| (cipher == WPA_CIPHER_NONE || cipher == e->cipher)) |
| return e; |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* |
| * ptksa_cache_list - Dump text list of entries in PTKSA cache |
| * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init() |
| * @buf: Buffer for the list |
| * @len: Length of the buffer |
| * Returns: Number of bytes written to buffer |
| * |
| * This function is used to generate a text format representation of the |
| * current PTKSA cache contents for the ctrl_iface PTKSA command. |
| */ |
| int ptksa_cache_list(struct ptksa_cache *ptksa, char *buf, size_t len) |
| { |
| struct ptksa_cache_entry *e; |
| int i = 0, ret; |
| char *pos = buf; |
| struct os_reltime now; |
| |
| if (!ptksa) |
| return 0; |
| |
| os_get_reltime(&now); |
| |
| ret = os_snprintf(pos, buf + len - pos, |
| "Index / ADDR / Cipher / expiration (secs) / TK / KDK\n"); |
| if (os_snprintf_error(buf + len - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| |
| dl_list_for_each(e, &ptksa->ptksa, struct ptksa_cache_entry, list) { |
| ret = os_snprintf(pos, buf + len - pos, "%u " MACSTR, |
| i, MAC2STR(e->addr)); |
| if (os_snprintf_error(buf + len - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| |
| ret = os_snprintf(pos, buf + len - pos, " %s %lu ", |
| wpa_cipher_txt(e->cipher), |
| e->expiration - now.sec); |
| if (os_snprintf_error(buf + len - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| |
| ret = wpa_snprintf_hex(pos, buf + len - pos, e->ptk.tk, |
| e->ptk.tk_len); |
| if (os_snprintf_error(buf + len - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| |
| ret = os_snprintf(pos, buf + len - pos, " "); |
| if (os_snprintf_error(buf + len - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| |
| ret = wpa_snprintf_hex(pos, buf + len - pos, e->ptk.kdk, |
| e->ptk.kdk_len); |
| if (os_snprintf_error(buf + len - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| |
| ret = os_snprintf(pos, buf + len - pos, "\n"); |
| if (os_snprintf_error(buf + len - pos, ret)) |
| return pos - buf; |
| pos += ret; |
| |
| i++; |
| } |
| |
| return pos - buf; |
| } |
| |
| |
| /* |
| * ptksa_cache_flush - Flush PTKSA cache entries |
| * |
| * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init() |
| * @addr: Peer address or %NULL to match any |
| * @cipher: Specific cipher suite to search for or WPA_CIPHER_NONE for any |
| */ |
| void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher) |
| { |
| struct ptksa_cache_entry *e, *next; |
| bool removed = false; |
| |
| if (!ptksa) |
| return; |
| |
| dl_list_for_each_safe(e, next, &ptksa->ptksa, struct ptksa_cache_entry, |
| list) { |
| if ((!addr || os_memcmp(e->addr, addr, ETH_ALEN) == 0) && |
| (cipher == WPA_CIPHER_NONE || cipher == e->cipher)) { |
| wpa_printf(MSG_DEBUG, |
| "Flush PTKSA cache entry for " MACSTR, |
| MAC2STR(e->addr)); |
| |
| ptksa_cache_free_entry(ptksa, e); |
| removed = true; |
| } |
| } |
| |
| if (removed) |
| ptksa_cache_set_expiration(ptksa); |
| } |
| |
| |
| /* |
| * ptksa_cache_add - Add a PTKSA cache entry |
| * @ptksa: Pointer to PTKSA cache data from ptksa_cache_init() |
| * @own_addr: Own MAC address |
| * @addr: Peer address |
| * @cipher: The cipher used |
| * @life_time: The PTK life time in seconds |
| * @ptk: The PTK |
| * @life_time_expiry_cb: Callback for alternative expiration handling |
| * @ctx: Context pointer to save into e->ctx for the callback |
| * @akmp: The key management mechanism that was used to derive the PTK |
| * Returns: Pointer to the added PTKSA cache entry or %NULL on error |
| * |
| * This function creates a PTKSA entry and adds it to the PTKSA cache. |
| * If an old entry is already in the cache for the same peer and cipher |
| * this entry will be replaced with the new entry. |
| */ |
| 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 (*life_time_expiry_cb) |
| (struct ptksa_cache_entry *e), |
| void *ctx, u32 akmp) |
| { |
| struct ptksa_cache_entry *entry, *tmp, *tmp2 = NULL; |
| struct os_reltime now; |
| bool set_expiry = false; |
| |
| if (!ptksa || !ptk || !addr || !life_time || cipher == WPA_CIPHER_NONE) |
| return NULL; |
| |
| /* remove a previous entry if present */ |
| ptksa_cache_flush(ptksa, addr, cipher); |
| |
| /* no place to add another entry */ |
| if (ptksa->n_ptksa >= PTKSA_CACHE_MAX_ENTRIES) |
| return NULL; |
| |
| entry = os_zalloc(sizeof(*entry)); |
| if (!entry) |
| return NULL; |
| |
| dl_list_init(&entry->list); |
| os_memcpy(entry->addr, addr, ETH_ALEN); |
| entry->cipher = cipher; |
| entry->cb = life_time_expiry_cb; |
| entry->ctx = ctx; |
| entry->akmp = akmp; |
| |
| if (own_addr) |
| os_memcpy(entry->own_addr, own_addr, ETH_ALEN); |
| |
| os_memcpy(&entry->ptk, ptk, sizeof(entry->ptk)); |
| |
| os_get_reltime(&now); |
| entry->expiration = now.sec + life_time; |
| |
| dl_list_for_each(tmp, &ptksa->ptksa, struct ptksa_cache_entry, list) { |
| if (tmp->expiration > entry->expiration) { |
| tmp2 = tmp; |
| break; |
| } |
| } |
| |
| if (dl_list_empty(&entry->list)) |
| set_expiry = true; |
| /* |
| * If the expiration is later then all other or the list is empty |
| * entries, add it to the end of the list; |
| * otherwise add it before the relevant entry. |
| */ |
| if (tmp2) |
| dl_list_add(&tmp2->list, &entry->list); |
| else |
| dl_list_add_tail(&ptksa->ptksa, &entry->list); |
| |
| ptksa->n_ptksa++; |
| wpa_printf(MSG_DEBUG, |
| "Added PTKSA cache entry addr=" MACSTR " cipher=%u", |
| MAC2STR(addr), cipher); |
| |
| if (set_expiry) |
| ptksa_cache_set_expiration(ptksa); |
| |
| return entry; |
| } |