diff --git a/src/common/Makefile b/src/common/Makefile index 59ba6c5e2..e2c5f03c2 100644 --- a/src/common/Makefile +++ b/src/common/Makefile @@ -3,12 +3,14 @@ CFLAGS += -DCONFIG_HS20 CFLAGS += -DCONFIG_SAE CFLAGS += -DCONFIG_SUITE CFLAGS += -DCONFIG_SUITEB +CFLAGS += -DCONFIG_PTKSA_CACHE LIB_OBJS= \ gas.o \ hw_features_common.o \ ieee802_11_common.o \ sae.o \ + ptksa_cache.o \ wpa_common.o include ../lib.rules diff --git a/src/common/ptksa_cache.c b/src/common/ptksa_cache.c new file mode 100644 index 000000000..6a053d650 --- /dev/null +++ b/src/common/ptksa_cache.c @@ -0,0 +1,321 @@ +/* + * 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)); + + 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() + * @addr: Peer address + * @cipher: The cipher used + * @life_time: The PTK life time in seconds + * @ptk: 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 *addr, u32 cipher, + u32 life_time, + const struct wpa_ptk *ptk) +{ + struct ptksa_cache_entry *entry, *tmp; + struct os_reltime now; + + 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; + + 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) + break; + } + + /* + * If the list was empty add to the head; otherwise if the expiration is + * later then all other entries, add it to the end of the list; + * otherwise add it before the relevant entry. + */ + if (!tmp) + dl_list_add(&ptksa->ptksa, &entry->list); + else if (tmp->expiration < entry->expiration) + dl_list_add(&tmp->list, &entry->list); + else + dl_list_add_tail(&tmp->list, &entry->list); + + ptksa->n_ptksa++; + wpa_printf(MSG_DEBUG, + "Added PTKSA cache entry addr=" MACSTR " cipher=%u", + MAC2STR(addr), cipher); + + return entry; +} diff --git a/src/common/ptksa_cache.h b/src/common/ptksa_cache.h new file mode 100644 index 000000000..28ef29144 --- /dev/null +++ b/src/common/ptksa_cache.h @@ -0,0 +1,79 @@ +/* + * RSN PTKSA cache interface + * + * Copyright (C) 2019 Intel Corporation + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef PTKSA_CACHE_H +#define PTKSA_CACHE_H + +#include "wpa_common.h" +#include "defs.h" +#include "list.h" + +/** + * struct ptksa_cache_entry - PTKSA cache entry + */ +struct ptksa_cache_entry { + struct dl_list list; + struct wpa_ptk ptk; + os_time_t expiration; + u32 cipher; + u8 addr[ETH_ALEN]; +}; + +#ifdef CONFIG_PTKSA_CACHE + +struct ptksa_cache; + +struct ptksa_cache * ptksa_cache_init(void); +void ptksa_cache_deinit(struct ptksa_cache *ptksa); +struct ptksa_cache_entry * ptksa_cache_get(struct ptksa_cache *ptksa, + const u8 *addr, u32 cipher); +int ptksa_cache_list(struct ptksa_cache *ptksa, char *buf, size_t len); +struct ptksa_cache_entry * ptksa_cache_add(struct ptksa_cache *ptksa, + const u8 *addr, u32 cipher, + u32 life_time, + const struct wpa_ptk *ptk); +void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher); + +#else /* CONFIG_PTKSA_CACHE */ + +static inline struct ptksa_cache * ptksa_cache_init(void) +{ + return (struct ptksa_cache *) 1; +} + +static inline void ptksa_cache_deinit(struct ptksa_cache *ptksa) +{ +} + +static inline struct ptksa_cache_entry * +ptksa_cache_get(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher) +{ + return NULL; +} + +static inline int ptksa_cache_list(struct ptksa_cache *ptksa, + char *buf, size_t len) +{ + return -1; +} + +static inline struct ptksa_cache_entry * +ptksa_cache_add(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher, + u32 life_time, const struct wpa_ptk *ptk) +{ + return NULL; +} + +static inline void ptksa_cache_flush(struct ptksa_cache *ptksa, + const u8 *addr, u32 cipher) +{ +} + +#endif /* CONFIG_PTKSA_CACHE */ +#endif /* PTKSA_CACHE_H */