From a4e36916168ae35bd87187e10d02b4da8f5a2600 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Wed, 16 Dec 2020 13:00:26 +0200 Subject: [PATCH] WPA: Add PTKSA cache implementation In order to be able to perform secure LTF measurements, both the initiator and the responder need to first derive TK and KDK and store them, so they would later be available for the secure LTF negotiation. Add a basic implementation of a PTKSA cache that stores derived TK/KDK which can later be used for secure LTF negotiation, and add it to the build configuration. Signed-off-by: Ilan Peer --- src/common/Makefile | 2 + src/common/ptksa_cache.c | 321 +++++++++++++++++++++++++++++++++++++++ src/common/ptksa_cache.h | 79 ++++++++++ 3 files changed, 402 insertions(+) create mode 100644 src/common/ptksa_cache.c create mode 100644 src/common/ptksa_cache.h 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 */